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">
|
<project version="4">
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<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>
|
</project>
|
||||||
|
|
@ -2,15 +2,15 @@ plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin' version '2.0.1'
|
id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin' version '2.0.1'
|
||||||
id 'org.jetbrains.kotlin.android'
|
id 'org.jetbrains.kotlin.android'
|
||||||
id 'kotlin-kapt'
|
id 'com.google.devtools.ksp'
|
||||||
id "org.jetbrains.kotlin.plugin.serialization" version "1.8.10"
|
id "org.jetbrains.kotlin.plugin.serialization" version "1.8.10"
|
||||||
}
|
}
|
||||||
|
|
||||||
def properties = new Properties()
|
def properties = new Properties()
|
||||||
def versionMajor = 1
|
def versionMajor = 1
|
||||||
def versionMinor = 6
|
def versionMinor = 6
|
||||||
def versionPatch = 3
|
def versionPatch = 5
|
||||||
def versionBuild = 17 // bump for dogfood builds, public betas, etc.
|
def versionBuild = 0 // bump for dogfood builds, public betas, etc.
|
||||||
def versionExt = ""
|
def versionExt = ""
|
||||||
|
|
||||||
if (versionBuild > 0){
|
if (versionBuild > 0){
|
||||||
|
|
@ -49,10 +49,8 @@ android {
|
||||||
useSupportLibrary true
|
useSupportLibrary true
|
||||||
}
|
}
|
||||||
|
|
||||||
kapt {
|
ksp {
|
||||||
arguments {
|
arg("room.schemaLocation", "$projectDir/schemas")
|
||||||
arg("room.schemaLocation", "$projectDir/schemas")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -76,6 +74,8 @@ android {
|
||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
debuggable false
|
debuggable false
|
||||||
signingConfig signingConfigs.debug
|
signingConfig signingConfigs.debug
|
||||||
|
applicationIdSuffix '.beta'
|
||||||
|
versionNameSuffix '-Beta'
|
||||||
}
|
}
|
||||||
|
|
||||||
debug {
|
debug {
|
||||||
|
|
@ -98,7 +98,7 @@ android {
|
||||||
disable 'MissingTranslation'
|
disable 'MissingTranslation'
|
||||||
}
|
}
|
||||||
|
|
||||||
android.applicationVariants.all { variant ->
|
android.applicationVariants.configureEach { variant ->
|
||||||
variant.outputs.each { output ->
|
variant.outputs.each { output ->
|
||||||
def baseAbiVersionCode = project.abiCodes.get(output.getFilter(com.android.build.OutputFile.ABI))
|
def baseAbiVersionCode = project.abiCodes.get(output.getFilter(com.android.build.OutputFile.ABI))
|
||||||
if (baseAbiVersionCode != null) {
|
if (baseAbiVersionCode != null) {
|
||||||
|
|
@ -149,7 +149,7 @@ dependencies {
|
||||||
implementation 'com.google.android.material:material:1.9.0-rc01'
|
implementation 'com.google.android.material:material:1.9.0-rc01'
|
||||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.3.1'
|
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-fragment-ktx:$navVer"
|
||||||
implementation "androidx.navigation:navigation-ui-ktx:$navVer"
|
implementation "androidx.navigation:navigation-ui-ktx:$navVer"
|
||||||
implementation 'androidx.core:core-ktx:1.10.1'
|
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-runtime:2.5.2"
|
||||||
implementation "androidx.room:room-ktx: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"
|
androidTestImplementation "androidx.room:room-testing:2.5.2"
|
||||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'
|
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'
|
||||||
implementation "androidx.compose.runtime:runtime:$composeVer"
|
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-android:$coroutineVer"
|
||||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$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 "com.squareup.okhttp3:okhttp:5.0.0-alpha.10"
|
||||||
|
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0'
|
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0'
|
||||||
|
|
@ -200,7 +202,7 @@ dependencies {
|
||||||
implementation("androidx.media3:media3-ui:$media3_version")
|
implementation("androidx.media3:media3-ui:$media3_version")
|
||||||
// For RTSP playback support with ExoPlayer
|
// For RTSP playback support with ExoPlayer
|
||||||
implementation("androidx.media3:media3-exoplayer-rtsp:$media3_version")
|
implementation("androidx.media3:media3-exoplayer-rtsp:$media3_version")
|
||||||
implementation("androidx.media3:media3-datasource-cronet:$media3_version")
|
|
||||||
|
|
||||||
implementation "com.anggrayudi:storage:1.5.5"
|
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" />
|
<data android:mimeType="text/plain" />
|
||||||
</intent-filter>
|
</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>
|
||||||
<activity
|
<activity
|
||||||
android:name=".receiver.ShareFileActivity"
|
android:name=".receiver.ShareFileActivity"
|
||||||
|
|
@ -96,6 +107,8 @@
|
||||||
android:name=".terminalShareAlias"
|
android:name=".terminalShareAlias"
|
||||||
android:targetActivity=".ui.more.TerminalActivity"
|
android:targetActivity=".ui.more.TerminalActivity"
|
||||||
android:enabled="false"
|
android:enabled="false"
|
||||||
|
android:icon="@mipmap/terminal_icon"
|
||||||
|
android:roundIcon="@mipmap/terminal_icon_round"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
|
@ -204,7 +217,7 @@
|
||||||
</service>
|
</service>
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="com.deniscerri.ytdl.fileprovider"
|
android:authorities="${applicationId}.fileprovider"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:grantUriPermissions="true">
|
android:grantUriPermissions="true">
|
||||||
|
|
@ -212,18 +225,6 @@
|
||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
android:resource="@xml/provider_paths" />
|
android:resource="@xml/provider_paths" />
|
||||||
</provider>
|
</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>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
@ -9,14 +9,11 @@ import androidx.work.Configuration
|
||||||
import androidx.work.Constraints
|
import androidx.work.Constraints
|
||||||
import androidx.work.ExistingPeriodicWorkPolicy
|
import androidx.work.ExistingPeriodicWorkPolicy
|
||||||
import androidx.work.NetworkType
|
import androidx.work.NetworkType
|
||||||
import androidx.work.PeriodicWorkRequest
|
|
||||||
import androidx.work.PeriodicWorkRequestBuilder
|
import androidx.work.PeriodicWorkRequestBuilder
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import androidx.work.WorkQuery
|
|
||||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||||
import com.deniscerri.ytdlnis.util.UpdateUtil
|
import com.deniscerri.ytdlnis.util.UpdateUtil
|
||||||
import com.deniscerri.ytdlnis.work.UpdateYTDLWorker
|
import com.deniscerri.ytdlnis.work.UpdateYTDLWorker
|
||||||
import com.google.android.gms.common.internal.Constants
|
|
||||||
import com.yausername.aria2c.Aria2c
|
import com.yausername.aria2c.Aria2c
|
||||||
import com.yausername.ffmpeg.FFmpeg
|
import com.yausername.ffmpeg.FFmpeg
|
||||||
import com.yausername.youtubedl_android.YoutubeDL
|
import com.yausername.youtubedl_android.YoutubeDL
|
||||||
|
|
@ -50,21 +47,27 @@ class App : Application() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//init yt-dlp auto update with work request
|
if (sharedPreferences.getBoolean("auto_update_ytdlp", true)){
|
||||||
val constraints = Constraints.Builder()
|
//init yt-dlp auto update with work request
|
||||||
.setRequiredNetworkType(NetworkType.UNMETERED)
|
val constraints = Constraints.Builder()
|
||||||
.setRequiresDeviceIdle(true)
|
.setRequiredNetworkType(NetworkType.UNMETERED)
|
||||||
.build()
|
.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){
|
}catch (e: Exception){
|
||||||
Looper.prepare().runCatching {
|
Looper.prepare().runCatching {
|
||||||
|
|
@ -73,14 +76,6 @@ class App : Application() {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WorkManager.initialize(
|
|
||||||
this@App,
|
|
||||||
Configuration.Builder()
|
|
||||||
.setExecutor(Executors.newFixedThreadPool(
|
|
||||||
sharedPreferences.getInt("concurrent_downloads", 1)))
|
|
||||||
.build())
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@Throws(YoutubeDLException::class)
|
@Throws(YoutubeDLException::class)
|
||||||
private fun initLibraries() {
|
private fun initLibraries() {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import android.content.SharedPreferences
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
|
@ -106,14 +107,14 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
||||||
}
|
}
|
||||||
|
|
||||||
val formatDetailsChip = card.findViewById<Chip>(R.id.format_note)
|
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)
|
val fileSize = FileUtil.convertFileSize(item.format.filesize)
|
||||||
if (fileSize != "?") formatDetailsText.append(" \t •\t $fileSize")
|
if (fileSize != "?") sideDetails.add(fileSize)
|
||||||
|
formatDetailsChip.text = sideDetails.joinToString(" · ")
|
||||||
formatDetailsChip.text = formatDetailsText
|
|
||||||
|
|
||||||
//OUTPUT
|
//OUTPUT
|
||||||
val output = card.findViewById<TextView>(R.id.output)
|
val output = card.findViewById<TextView>(R.id.output)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package com.deniscerri.ytdlnis.adapter
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.graphics.Color
|
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
|
@ -11,10 +10,10 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.paging.PagingDataAdapter
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
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.google.android.material.card.MaterialCardView
|
||||||
import com.squareup.picasso.Picasso
|
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 onItemClickListener: OnItemClickListener
|
||||||
private val activity: Activity
|
private val activity: Activity
|
||||||
private val checkedItems: ArrayList<Long>
|
val checkedItems: ArrayList<Long>
|
||||||
|
var inverted: Boolean
|
||||||
private val sharedPreferences: SharedPreferences
|
private val sharedPreferences: SharedPreferences
|
||||||
|
|
||||||
init {
|
init {
|
||||||
checkedItems = ArrayList()
|
checkedItems = ArrayList()
|
||||||
this.onItemClickListener = onItemClickListener
|
this.onItemClickListener = onItemClickListener
|
||||||
this.activity = activity
|
this.activity = activity
|
||||||
|
this.inverted = false
|
||||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,13 +54,16 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
val item = getItem(position)
|
val item = getItem(position)
|
||||||
val card = holder.cardView
|
val card = holder.cardView
|
||||||
|
card.isVisible = item != null
|
||||||
|
if (item == null) return
|
||||||
|
card.tag = item.id.toString()
|
||||||
|
|
||||||
val uiHandler = Handler(Looper.getMainLooper())
|
val uiHandler = Handler(Looper.getMainLooper())
|
||||||
val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view)
|
val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view)
|
||||||
|
|
||||||
// THUMBNAIL ----------------------------------
|
// THUMBNAIL ----------------------------------
|
||||||
if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("queue")){
|
if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("queue")){
|
||||||
val imageURL = item!!.thumb
|
val imageURL = item.thumb
|
||||||
if (imageURL.isNotEmpty()) {
|
if (imageURL.isNotEmpty()) {
|
||||||
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
|
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -127,7 +131,7 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
||||||
actionButton.setOnClickListener {
|
actionButton.setOnClickListener {
|
||||||
onItemClickListener.onActionButtonClick(item.id)
|
onItemClickListener.onActionButtonClick(item.id)
|
||||||
}
|
}
|
||||||
if (checkedItems.contains(item.id)) {
|
if ((checkedItems.contains(item.id) && !inverted) || (!checkedItems.contains(item.id) && inverted)) {
|
||||||
card.isChecked = true
|
card.isChecked = true
|
||||||
card.strokeWidth = 5
|
card.strokeWidth = 5
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -135,67 +139,69 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
||||||
card.strokeWidth = 0
|
card.strokeWidth = 0
|
||||||
}
|
}
|
||||||
card.setOnClickListener {
|
card.setOnClickListener {
|
||||||
if (checkedItems.size > 0) {
|
if (checkedItems.size > 0 || inverted) {
|
||||||
checkCard(card, item.id)
|
checkCard(card, item.id, position)
|
||||||
} else {
|
} else {
|
||||||
onItemClickListener.onCardClick(item.id)
|
onItemClickListener.onCardClick(item.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
card.setOnLongClickListener {
|
card.setOnLongClickListener {
|
||||||
checkCard(card, item.id)
|
checkCard(card, item.id, position)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
fun clearCheckeditems() {
|
fun clearCheckedItems() {
|
||||||
for (i in 0 until itemCount){
|
inverted = false
|
||||||
val item = getItem(i)
|
|
||||||
if (checkedItems.find { it == item?.id } != null){
|
|
||||||
checkedItems.remove(item?.id)
|
|
||||||
notifyItemChanged(i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checkedItems.clear()
|
checkedItems.clear()
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
|
||||||
fun checkAll(items: List<DownloadItem?>?){
|
|
||||||
checkedItems.clear()
|
|
||||||
checkedItems.addAll(items!!.map { it!!.id })
|
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
fun invertSelected(items: List<DownloadItem?>?){
|
fun checkAll() {
|
||||||
val invertedList = mutableListOf<Long>()
|
|
||||||
items?.forEach {
|
|
||||||
if (!checkedItems.contains(it!!.id)) invertedList.add(it.id)
|
|
||||||
}
|
|
||||||
checkedItems.clear()
|
checkedItems.clear()
|
||||||
checkedItems.addAll(invertedList)
|
inverted = true
|
||||||
notifyDataSetChanged()
|
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) {
|
if (card.isChecked) {
|
||||||
card.strokeWidth = 0
|
card.strokeWidth = 0
|
||||||
checkedItems.remove(itemID)
|
if (inverted) checkedItems.add(itemID)
|
||||||
|
else checkedItems.remove(itemID)
|
||||||
} else {
|
} else {
|
||||||
card.strokeWidth = 5
|
card.strokeWidth = 5
|
||||||
checkedItems.add(itemID)
|
if (inverted) checkedItems.remove(itemID)
|
||||||
|
else checkedItems.add(itemID)
|
||||||
}
|
}
|
||||||
|
|
||||||
card.isChecked = !card.isChecked
|
card.isChecked = !card.isChecked
|
||||||
onItemClickListener.onCardSelect(itemID, card.isChecked)
|
onItemClickListener.onCardSelect(card.isChecked, position)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OnItemClickListener {
|
interface OnItemClickListener {
|
||||||
fun onActionButtonClick(itemID: Long)
|
fun onActionButtonClick(itemID: Long)
|
||||||
fun onCardClick(itemID: Long)
|
fun onCardClick(itemID: Long)
|
||||||
fun onCardSelect(itemID: Long, isChecked: Boolean)
|
fun onCardSelect(isChecked: Boolean, position: Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
|
import org.w3c.dom.Text
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -70,17 +71,9 @@ class PlaylistAdapter(onItemClickListener: OnItemClickListener, activity: Activi
|
||||||
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
|
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// TITLE ----------------------------------
|
card.findViewById<TextView>(R.id.title).text = item!!.title
|
||||||
val itemTitle = card.findViewById<TextView>(R.id.title)
|
card.findViewById<TextView>(R.id.author).text = item.author
|
||||||
var title = item!!.title
|
card.findViewById<TextView>(R.id.duration).text = item.duration
|
||||||
if (title.length > 100) {
|
|
||||||
title = title.substring(0, 40) + "..."
|
|
||||||
}
|
|
||||||
itemTitle.text = title
|
|
||||||
|
|
||||||
|
|
||||||
val duration = card.findViewById<TextView>(R.id.duration)
|
|
||||||
duration.text = item.duration
|
|
||||||
|
|
||||||
// CHECKBOX ----------------------------------
|
// CHECKBOX ----------------------------------
|
||||||
val check = card.findViewById<CheckBox>(R.id.checkBox)
|
val check = card.findViewById<CheckBox>(R.id.checkBox)
|
||||||
|
|
|
||||||
|
|
@ -13,16 +13,9 @@ import java.lang.reflect.Type
|
||||||
|
|
||||||
class Converters {
|
class Converters {
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun stringToListOfFormats(value: String?): ArrayList<Format> {
|
fun stringToListOfFormats(value: String) = Gson().fromJson(value, Array<Format>::class.java).toMutableList()
|
||||||
val listType: Type = object : TypeToken<ArrayList<Format?>?>() {}.type
|
|
||||||
return Gson().fromJson(value, listType)
|
|
||||||
}
|
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun listOfFormatsToString(list: ArrayList<Format?>?): String {
|
fun listOfFormatsToString(list: List<Format?>?) = Gson().toJson(list).toString()
|
||||||
val gson = Gson()
|
|
||||||
return gson.toJson(list)
|
|
||||||
}
|
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun formatToString(format: Format): String = Gson().toJson(format)
|
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)
|
fun stringToVideoPreferences(string: String): VideoPreferences = Gson().fromJson(string, VideoPreferences::class.java)
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun stringToListOfChapters(value: String?): ArrayList<ChapterItem>? {
|
fun stringToListOfChapters(value: String?) = Gson().fromJson(value, Array<ChapterItem>::class.java).toMutableList()
|
||||||
val listType: Type = object : TypeToken<ArrayList<ChapterItem?>?>() {}.type
|
|
||||||
return Gson().fromJson(value, listType)
|
|
||||||
}
|
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun listOfChaptersToString(list: ArrayList<ChapterItem?>?): String {
|
fun listOfChaptersToString(list: List<ChapterItem?>?): String {
|
||||||
val gson = Gson()
|
val gson = Gson()
|
||||||
return gson.toJson(list)
|
return gson.toJson(list)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,14 @@
|
||||||
package com.deniscerri.ytdlnis.database
|
package com.deniscerri.ytdlnis.database
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.paging.Pager
|
||||||
|
import androidx.paging.PagingConfig
|
||||||
|
import androidx.paging.PagingData
|
||||||
|
import androidx.paging.PagingSource
|
||||||
import androidx.room.*
|
import androidx.room.*
|
||||||
import com.deniscerri.ytdlnis.database.dao.*
|
import com.deniscerri.ytdlnis.database.dao.*
|
||||||
import com.deniscerri.ytdlnis.database.models.*
|
import com.deniscerri.ytdlnis.database.models.*
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
@TypeConverters(Converters::class)
|
@TypeConverters(Converters::class)
|
||||||
@Database(
|
@Database(
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,26 @@
|
||||||
package com.deniscerri.ytdlnis.database.dao
|
package com.deniscerri.ytdlnis.database.dao
|
||||||
|
|
||||||
|
import androidx.paging.PagingSource
|
||||||
import androidx.room.*
|
import androidx.room.*
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
|
import com.deniscerri.ytdlnis.database.models.Format
|
||||||
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
interface DownloadDao {
|
interface DownloadDao {
|
||||||
|
|
||||||
@Query("SELECT * FROM downloads ORDER BY status")
|
@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'")
|
@Query("SELECT * FROM downloads WHERE status='Active' or status='Paused'")
|
||||||
fun getActiveDownloads() : Flow<List<DownloadItem>>
|
fun getActiveDownloads() : Flow<List<DownloadItem>>
|
||||||
|
|
||||||
@Query("SELECT COUNT(*) FROM downloads WHERE status='Active' or status='Paused'")
|
@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'")
|
@Query("SELECT * FROM downloads WHERE status='Active' or status='Paused'")
|
||||||
fun getActiveDownloadsList() : List<DownloadItem>
|
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'")
|
@Query("SELECT * FROM downloads WHERE status='Active' or status='Queued' or status='QueuedPaused' or status='Paused'")
|
||||||
fun getActiveAndQueuedDownloadsList() : List<DownloadItem>
|
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")
|
@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")
|
@Query("SELECT * FROM downloads WHERE status='Queued' or status='QueuedPaused' ORDER BY downloadStartTime, id")
|
||||||
fun getQueuedDownloadsList() : List<DownloadItem>
|
fun getQueuedDownloadsList() : List<DownloadItem>
|
||||||
|
|
||||||
@Query("SELECT * FROM downloads WHERE status='Cancelled' ORDER BY id DESC")
|
@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")
|
@Query("SELECT * FROM downloads WHERE status='Cancelled' ORDER BY id DESC")
|
||||||
fun getCancelledDownloadsList() : List<DownloadItem>
|
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")
|
@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")
|
@Query("SELECT * FROM downloads WHERE status='Error' ORDER BY id DESC")
|
||||||
fun getErroredDownloadsList() : List<DownloadItem>
|
fun getErroredDownloadsList() : List<DownloadItem>
|
||||||
|
|
||||||
@Query("SELECT * FROM downloads WHERE status='Saved' ORDER BY id DESC")
|
@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")
|
@Query("SELECT * FROM downloads WHERE id=:id LIMIT 1")
|
||||||
fun getDownloadById(id: Long) : DownloadItem
|
fun getDownloadById(id: Long) : DownloadItem
|
||||||
|
|
@ -52,6 +73,22 @@ interface DownloadDao {
|
||||||
@Query("SELECT status FROM downloads WHERE id=:id")
|
@Query("SELECT status FROM downloads WHERE id=:id")
|
||||||
fun checkStatus(id: Long) : String
|
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)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
suspend fun insert(item: DownloadItem) : Long
|
suspend fun insert(item: DownloadItem) : Long
|
||||||
|
|
||||||
|
|
@ -73,7 +110,7 @@ interface DownloadDao {
|
||||||
@Query("DELETE FROM downloads WHERE status='Saved'")
|
@Query("DELETE FROM downloads WHERE status='Saved'")
|
||||||
suspend fun deleteSaved()
|
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()
|
suspend fun cancelQueued()
|
||||||
|
|
||||||
@Query("DELETE FROM downloads WHERE status='Processing' AND id=:id")
|
@Query("DELETE FROM downloads WHERE status='Processing' AND id=:id")
|
||||||
|
|
@ -82,6 +119,9 @@ interface DownloadDao {
|
||||||
@Upsert
|
@Upsert
|
||||||
suspend fun update(item: DownloadItem)
|
suspend fun update(item: DownloadItem)
|
||||||
|
|
||||||
|
@Upsert
|
||||||
|
fun updateMultiple(items :List<DownloadItem>)
|
||||||
|
|
||||||
@Query("SELECT * FROM downloads ORDER BY id DESC LIMIT 1")
|
@Query("SELECT * FROM downloads ORDER BY id DESC LIMIT 1")
|
||||||
fun getLatest() : DownloadItem
|
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")
|
@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
|
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>
|
fun getAllByKeyword(keyword: String) : List<SearchHistoryItem>
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
suspend fun insert(new: SearchHistoryItem)
|
suspend fun insert(newItem: SearchHistoryItem)
|
||||||
|
|
||||||
@Query("DELETE FROM searchHistory")
|
@Query("DELETE FROM searchHistory")
|
||||||
suspend fun deleteAll()
|
suspend fun deleteAll()
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ data class DownloadItem(
|
||||||
var container: String,
|
var container: String,
|
||||||
@ColumnInfo(defaultValue = "")
|
@ColumnInfo(defaultValue = "")
|
||||||
var downloadSections: String,
|
var downloadSections: String,
|
||||||
val allFormats: ArrayList<Format>,
|
val allFormats: MutableList<Format>,
|
||||||
var downloadPath: String,
|
var downloadPath: String,
|
||||||
var website: String,
|
var website: String,
|
||||||
val downloadSize: String,
|
val downloadSize: String,
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,9 @@ data class ResultItem(
|
||||||
val thumb: String,
|
val thumb: String,
|
||||||
val website: String,
|
val website: String,
|
||||||
var playlistTitle: String,
|
var playlistTitle: String,
|
||||||
var formats: ArrayList<Format>,
|
var formats: MutableList<Format>,
|
||||||
@ColumnInfo(defaultValue = "")
|
@ColumnInfo(defaultValue = "")
|
||||||
var urls: String,
|
var urls: String,
|
||||||
var chapters: ArrayList<ChapterItem>?,
|
var chapters: MutableList<ChapterItem>?,
|
||||||
var creationTime: Long = System.currentTimeMillis() / 1000,
|
var creationTime: Long = System.currentTimeMillis() / 1000,
|
||||||
)
|
)
|
||||||
|
|
@ -1,19 +1,36 @@
|
||||||
package com.deniscerri.ytdlnis.database.repository
|
package com.deniscerri.ytdlnis.database.repository
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.paging.Pager
|
||||||
import com.deniscerri.ytdlnis.database.Converters
|
import androidx.paging.PagingConfig
|
||||||
import com.deniscerri.ytdlnis.database.dao.DownloadDao
|
import com.deniscerri.ytdlnis.database.dao.DownloadDao
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
|
||||||
class DownloadRepository(private val downloadDao: DownloadDao) {
|
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 activeDownloads : Flow<List<DownloadItem>> = downloadDao.getActiveDownloads()
|
||||||
val activeDownloadsCount : Flow<Int> = downloadDao.getActiveDownloadsCount()
|
val queuedDownloads : Pager<Int, DownloadItem> = Pager(
|
||||||
val queuedDownloads : Flow<List<DownloadItem>> = downloadDao.getQueuedDownloads()
|
config = PagingConfig(pageSize = 20, initialLoadSize = 20, prefetchDistance = 1),
|
||||||
val cancelledDownloads : Flow<List<DownloadItem>> = downloadDao.getCancelledDownloads()
|
pagingSourceFactory = {downloadDao.getQueuedDownloads()}
|
||||||
val erroredDownloads : Flow<List<DownloadItem>> = downloadDao.getErroredDownloads()
|
)
|
||||||
val savedDownloads : Flow<List<DownloadItem>> = downloadDao.getSavedDownloads()
|
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 {
|
enum class Status {
|
||||||
Active, Paused, Queued, QueuedPaused, Error, Cancelled, Saved
|
Active, Paused, Queued, QueuedPaused, Error, Cancelled, Saved
|
||||||
|
|
@ -27,8 +44,8 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
||||||
return downloadDao.insertAll(items)
|
return downloadDao.insertAll(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun delete(item: DownloadItem){
|
suspend fun delete(id: Long){
|
||||||
downloadDao.delete(item.id)
|
downloadDao.delete(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun update(item: DownloadItem){
|
suspend fun update(item: DownloadItem){
|
||||||
|
|
@ -61,6 +78,10 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
||||||
return downloadDao.getCancelledDownloadsList()
|
return downloadDao.getCancelledDownloadsList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getPausedDownloads() : List<DownloadItem> {
|
||||||
|
return downloadDao.getPausedDownloadsList()
|
||||||
|
}
|
||||||
|
|
||||||
fun getErroredDownloads() : List<DownloadItem> {
|
fun getErroredDownloads() : List<DownloadItem> {
|
||||||
return downloadDao.getErroredDownloadsList()
|
return downloadDao.getErroredDownloadsList()
|
||||||
}
|
}
|
||||||
|
|
@ -81,20 +102,12 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
||||||
downloadDao.cancelQueued()
|
downloadDao.cancelQueued()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLastDownloadId() : Long {
|
fun pauseDownloads(){
|
||||||
return downloadDao.getLastDownloadId()
|
downloadDao.pauseActiveAndQueued()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun unPauseDownloads(){
|
||||||
fun checkIfReDownloadingErroredOrCancelled(item: DownloadItem) : Long {
|
downloadDao.unPauseActiveAndQueued()
|
||||||
val converters = Converters()
|
|
||||||
val format = converters.formatToString(item.format)
|
|
||||||
return try {
|
|
||||||
val i = downloadDao.getUnfinishedByURLAndFormat(item.url, format)
|
|
||||||
i.id
|
|
||||||
}catch (e: Exception){
|
|
||||||
0L
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -12,23 +12,24 @@ import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.asLiveData
|
import androidx.lifecycle.asLiveData
|
||||||
import androidx.lifecycle.viewModelScope
|
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.DBManager
|
||||||
import com.deniscerri.ytdlnis.database.models.CookieItem
|
import com.deniscerri.ytdlnis.database.models.CookieItem
|
||||||
import com.deniscerri.ytdlnis.database.repository.CookieRepository
|
import com.deniscerri.ytdlnis.database.repository.CookieRepository
|
||||||
import com.deniscerri.ytdlnis.ui.more.WebViewActivity
|
import com.deniscerri.ytdlnis.ui.more.WebViewActivity
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
class CookieViewModel(private val application: Application) : AndroidViewModel(application) {
|
class CookieViewModel(private val application: Application) : AndroidViewModel(application) {
|
||||||
private val repository: CookieRepository
|
private val repository: CookieRepository
|
||||||
val items: LiveData<List<CookieItem>>
|
val items: LiveData<List<CookieItem>>
|
||||||
private val jsonFormat = Json { prettyPrint = true }
|
|
||||||
|
|
||||||
private val cookieHeader = "# Netscape HTTP Cookie File\n" +
|
private val cookieHeader =
|
||||||
"# WebView Generated by the YTDLnis app\n"
|
"# Netscape HTTP Cookie File\n" +
|
||||||
|
"# WebView Generated by the YTDLnis app\n" +
|
||||||
|
"# This is a generated file! Do not edit."
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val dao = DBManager.getInstance(application).cookieDao
|
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!")
|
if (!hasCookies()) throw Exception("There is no cookies in the database!")
|
||||||
flush()
|
flush()
|
||||||
}
|
}
|
||||||
|
val dbPath = File("/data/data/${BuildConfig.APPLICATION_ID}/").walkTopDown().find { it.name == "Cookies" }
|
||||||
val dbPath = File("/data/data/com.deniscerri.ytdl/").walkTopDown().find { it.name == "Cookies" }
|
|
||||||
?: throw Exception("Cookies File not found!")
|
?: throw Exception("Cookies File not found!")
|
||||||
|
|
||||||
SQLiteDatabase.openDatabase(
|
SQLiteDatabase.openDatabase(
|
||||||
|
|
@ -139,11 +139,12 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a
|
||||||
application.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
application.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
var clip = clipboard.primaryClip!!.getItemAt(0).text
|
var clip = clipboard.primaryClip!!.getItemAt(0).text
|
||||||
Log.e("Aaa", clip.toString())
|
Log.e("Aaa", clip.toString())
|
||||||
if (clip.startsWith(cookieHeader)){
|
|
||||||
|
if (clip.startsWith("# Netscape HTTP Cookie File")){
|
||||||
clip = clip.removePrefix(cookieHeader)
|
clip = clip.removePrefix(cookieHeader)
|
||||||
val cookie = CookieItem(
|
val cookie = CookieItem(
|
||||||
0,
|
0,
|
||||||
"Cookie Import [${System.currentTimeMillis()}]",
|
"Cookie Import at [${Date()}]",
|
||||||
clip.toString()
|
clip.toString()
|
||||||
)
|
)
|
||||||
insert(cookie)
|
insert(cookie)
|
||||||
|
|
|
||||||
|
|
@ -8,22 +8,20 @@ import android.content.res.Resources
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.util.DisplayMetrics
|
import android.util.DisplayMetrics
|
||||||
import android.util.Log
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.asLiveData
|
import androidx.lifecycle.asLiveData
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import androidx.paging.PagingData
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.work.Constraints
|
import androidx.work.Constraints
|
||||||
import androidx.work.Data
|
|
||||||
import androidx.work.ExistingWorkPolicy
|
import androidx.work.ExistingWorkPolicy
|
||||||
import androidx.work.NetworkType
|
import androidx.work.NetworkType
|
||||||
import androidx.work.OneTimeWorkRequest
|
|
||||||
import androidx.work.OneTimeWorkRequestBuilder
|
import androidx.work.OneTimeWorkRequestBuilder
|
||||||
|
import androidx.work.WorkInfo
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import androidx.work.WorkRequest
|
import androidx.work.await
|
||||||
import androidx.work.workDataOf
|
|
||||||
import com.deniscerri.ytdlnis.App
|
import com.deniscerri.ytdlnis.App
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.DBManager
|
import com.deniscerri.ytdlnis.database.DBManager
|
||||||
|
|
@ -44,18 +42,12 @@ import com.deniscerri.ytdlnis.work.DownloadWorker
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.async
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.collect
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.UUID
|
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.random.Random
|
|
||||||
import kotlin.random.nextInt
|
|
||||||
|
|
||||||
|
|
||||||
class DownloadViewModel(application: Application) : AndroidViewModel(application) {
|
class DownloadViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
|
|
@ -64,13 +56,13 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
private val sharedPreferences: SharedPreferences
|
private val sharedPreferences: SharedPreferences
|
||||||
private val commandTemplateDao: CommandTemplateDao
|
private val commandTemplateDao: CommandTemplateDao
|
||||||
private val infoUtil : InfoUtil
|
private val infoUtil : InfoUtil
|
||||||
val allDownloads : LiveData<List<DownloadItem>>
|
val allDownloads : Flow<PagingData<DownloadItem>>
|
||||||
val queuedDownloads : LiveData<List<DownloadItem>>
|
val queuedDownloads : Flow<PagingData<DownloadItem>>
|
||||||
val activeDownloads : LiveData<List<DownloadItem>>
|
val activeDownloads : LiveData<List<DownloadItem>>
|
||||||
val activeDownloadsCount : LiveData<Int>
|
val activeDownloadsCount : LiveData<Int>
|
||||||
val cancelledDownloads : LiveData<List<DownloadItem>>
|
val cancelledDownloads : Flow<PagingData<DownloadItem>>
|
||||||
val erroredDownloads : LiveData<List<DownloadItem>>
|
val erroredDownloads : Flow<PagingData<DownloadItem>>
|
||||||
val savedDownloads : LiveData<List<DownloadItem>>
|
val savedDownloads : Flow<PagingData<DownloadItem>>
|
||||||
|
|
||||||
private var bestVideoFormat : Format
|
private var bestVideoFormat : Format
|
||||||
private var bestAudioFormat : Format
|
private var bestAudioFormat : Format
|
||||||
|
|
@ -81,6 +73,11 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
private val audioFormatIDPreference: List<String>
|
private val audioFormatIDPreference: List<String>
|
||||||
private val resources : Resources
|
private val resources : Resources
|
||||||
private var extraCommands: String = ""
|
private var extraCommands: String = ""
|
||||||
|
|
||||||
|
private var audioContainer: String?
|
||||||
|
private var videoContainer: String?
|
||||||
|
private var videoCodec: String?
|
||||||
|
|
||||||
enum class Type {
|
enum class Type {
|
||||||
audio, video, command
|
audio, video, command
|
||||||
}
|
}
|
||||||
|
|
@ -93,13 +90,13 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
commandTemplateDao = DBManager.getInstance(application).commandTemplateDao
|
commandTemplateDao = DBManager.getInstance(application).commandTemplateDao
|
||||||
infoUtil = InfoUtil(application)
|
infoUtil = InfoUtil(application)
|
||||||
|
|
||||||
allDownloads = repository.allDownloads.asLiveData()
|
allDownloads = repository.allDownloads.flow
|
||||||
queuedDownloads = repository.queuedDownloads.asLiveData()
|
queuedDownloads = repository.queuedDownloads.flow
|
||||||
activeDownloads = repository.activeDownloads.asLiveData()
|
activeDownloads = repository.activeDownloads.asLiveData()
|
||||||
activeDownloadsCount = repository.activeDownloadsCount.asLiveData()
|
activeDownloadsCount = repository.activeDownloadsCount.asLiveData()
|
||||||
savedDownloads = repository.savedDownloads.asLiveData()
|
savedDownloads = repository.savedDownloads.flow
|
||||||
cancelledDownloads = repository.cancelledDownloads.asLiveData()
|
cancelledDownloads = repository.cancelledDownloads.flow
|
||||||
erroredDownloads = repository.erroredDownloads.asLiveData()
|
erroredDownloads = repository.erroredDownloads.flow
|
||||||
viewModelScope.launch(Dispatchers.IO){
|
viewModelScope.launch(Dispatchers.IO){
|
||||||
if (sharedPreferences.getBoolean("use_extra_commands", false)){
|
if (sharedPreferences.getBoolean("use_extra_commands", false)){
|
||||||
extraCommands = commandTemplateDao.getAllTemplatesAsExtraCommands().joinToString(" ")
|
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()
|
videoQualityPreference = sharedPreferences.getString("video_quality", application.getString(R.string.best_quality)).toString()
|
||||||
formatIDPreference = sharedPreferences.getString("format_id", "").toString().split(",")
|
formatIDPreference = sharedPreferences.getString("format_id", "").toString().split(",").filter { it.isNotEmpty() }
|
||||||
audioFormatIDPreference = sharedPreferences.getString("format_id_audio", "").toString().split(",")
|
audioFormatIDPreference = sharedPreferences.getString("format_id_audio", "").toString().split(",").filter { it.isNotEmpty() }
|
||||||
|
|
||||||
val confTmp = Configuration(application.resources.configuration)
|
val confTmp = Configuration(application.resources.configuration)
|
||||||
confTmp.locale = Locale(sharedPreferences.getString("app_language", "en")!!)
|
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 videoFormatValues = resources.getStringArray(R.array.video_formats_values)
|
||||||
val videoContainer = sharedPreferences.getString("video_format", "Default")
|
videoContainer = sharedPreferences.getString("video_format", "Default")
|
||||||
|
|
||||||
defaultVideoFormats = mutableListOf()
|
defaultVideoFormats = mutableListOf()
|
||||||
videoFormatValues.forEach {
|
videoFormatValues.forEach {
|
||||||
|
|
@ -132,29 +129,55 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
)
|
)
|
||||||
defaultVideoFormats.add(tmp)
|
defaultVideoFormats.add(tmp)
|
||||||
}
|
}
|
||||||
|
formatIDPreference.reversed().forEach {
|
||||||
|
val tmp = Format(
|
||||||
|
it,
|
||||||
|
videoContainer!!,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
0,
|
||||||
|
it
|
||||||
|
)
|
||||||
|
defaultVideoFormats.add(tmp)
|
||||||
|
}
|
||||||
|
|
||||||
bestVideoFormat = defaultVideoFormats.last()
|
bestVideoFormat = defaultVideoFormats.last()
|
||||||
|
|
||||||
val audioContainer = sharedPreferences.getString("audio_format", "mp3")
|
audioContainer = sharedPreferences.getString("audio_format", "mp3")
|
||||||
bestAudioFormat = Format(
|
bestAudioFormat = if (audioFormatIDPreference.isEmpty()){
|
||||||
"best",
|
Format(
|
||||||
audioContainer!!,
|
"best",
|
||||||
"",
|
audioContainer!!,
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
0,
|
"",
|
||||||
"best"
|
0,
|
||||||
)
|
"best"
|
||||||
|
)
|
||||||
|
}else{
|
||||||
|
Format(
|
||||||
|
audioFormatIDPreference.first(),
|
||||||
|
audioContainer!!,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
0,
|
||||||
|
audioFormatIDPreference.first()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
videoCodec = sharedPreferences.getString("video_codec", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteDownload(item: DownloadItem) = viewModelScope.launch(Dispatchers.IO) {
|
fun deleteDownload(id: Long) = viewModelScope.launch(Dispatchers.IO) {
|
||||||
repository.delete(item)
|
repository.delete(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateDownload(item: DownloadItem){
|
suspend fun updateDownload(item: DownloadItem){
|
||||||
if (sharedPreferences.getBoolean("incognito", false)){
|
if (sharedPreferences.getBoolean("incognito", false)){
|
||||||
if (item.status == DownloadRepository.Status.Cancelled.toString() || item.status == DownloadRepository.Status.Error.toString()){
|
if (item.status == DownloadRepository.Status.Cancelled.toString() || item.status == DownloadRepository.Status.Error.toString()){
|
||||||
repository.delete(item)
|
repository.delete(item.id)
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
repository.update(item)
|
repository.update(item)
|
||||||
|
|
@ -345,7 +368,10 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
return cloneFormat (
|
return cloneFormat (
|
||||||
try {
|
try {
|
||||||
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){
|
}catch (e: Exception){
|
||||||
formats.last { it.format_note.contains("audio", ignoreCase = true) }
|
formats.last { it.format_note.contains("audio", ignoreCase = true) }
|
||||||
}
|
}
|
||||||
|
|
@ -358,24 +384,19 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
Type.video -> {
|
Type.video -> {
|
||||||
return cloneFormat(
|
return cloneFormat(
|
||||||
try {
|
try {
|
||||||
val theFormats = formats.ifEmpty { defaultVideoFormats }
|
val theFormats = formats.filter { !it.format_note.contains("audio", true) }.ifEmpty { defaultVideoFormats }
|
||||||
try {
|
when (videoQualityPreference) {
|
||||||
formats.first { !it.format_note.contains("audio", ignoreCase = true) && formatIDPreference.contains(it.format_id)}
|
"worst" -> {
|
||||||
}catch (e: Exception){
|
theFormats.first()
|
||||||
when (videoQualityPreference) {
|
}
|
||||||
"worst" -> {
|
else /*best*/ -> {
|
||||||
theFormats.first {!it.format_note.contains("audio", ignoreCase = true) }
|
val requirements: MutableList<(Format) -> Boolean> = mutableListOf()
|
||||||
}
|
requirements.add { it: Format -> formatIDPreference.contains(it.format_id) }
|
||||||
"best" -> {
|
requirements.add { it: Format -> if (videoContainer == "mp4") it.container.equals("mpeg_4", true) else it.container.equals(videoContainer, true)}
|
||||||
theFormats.last {!it.format_note.contains("audio", ignoreCase = true) }
|
requirements.add { it: Format -> it.format_note.contains(videoQualityPreference.substring(0, videoQualityPreference.length - 1)) }
|
||||||
}
|
requirements.add { it: Format -> it.vcodec.startsWith(videoCodec!!, true) }
|
||||||
else -> {
|
|
||||||
try{
|
theFormats.reversed().maxByOrNull { f -> requirements.count{ req -> req(f)} } ?: throw Exception()
|
||||||
theFormats.last {it.format_note.contains(videoQualityPreference.substring(0, videoQualityPreference.length - 1)) }
|
|
||||||
}catch (e: Exception){
|
|
||||||
theFormats.last { !it.format_note.contains("audio", ignoreCase = true) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}catch (e: Exception){
|
}catch (e: Exception){
|
||||||
|
|
@ -406,6 +427,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
val audioFormats = resources.getStringArray(R.array.audio_formats)
|
val audioFormats = resources.getStringArray(R.array.audio_formats)
|
||||||
val formats = mutableListOf<Format>()
|
val formats = mutableListOf<Format>()
|
||||||
val containerPreference = sharedPreferences.getString("audio_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)) }
|
audioFormats.forEach { formats.add(Format(it, containerPreference!!,"","", "",0, it)) }
|
||||||
return formats
|
return formats
|
||||||
}
|
}
|
||||||
|
|
@ -414,6 +436,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
val videoFormats = resources.getStringArray(R.array.video_formats_values)
|
val videoFormats = resources.getStringArray(R.array.video_formats_values)
|
||||||
val formats = mutableListOf<Format>()
|
val formats = mutableListOf<Format>()
|
||||||
val containerPreference = sharedPreferences.getString("video_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)) }
|
videoFormats.forEach { formats.add(Format(it, containerPreference!!,"Default","", "",0, it)) }
|
||||||
return formats
|
return formats
|
||||||
}
|
}
|
||||||
|
|
@ -436,6 +459,14 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
repository.insert(item)
|
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){
|
fun insertAll(items: List<DownloadItem>)= viewModelScope.launch(Dispatchers.IO){
|
||||||
items.forEach{
|
items.forEach{
|
||||||
repository.insert(it)
|
repository.insert(it)
|
||||||
|
|
@ -466,6 +497,10 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
return repository.getCancelledDownloads()
|
return repository.getCancelledDownloads()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getPausedDownloads() : List<DownloadItem> {
|
||||||
|
return repository.getPausedDownloads()
|
||||||
|
}
|
||||||
|
|
||||||
fun getErrored() : List<DownloadItem> {
|
fun getErrored() : List<DownloadItem> {
|
||||||
return repository.getErroredDownloads()
|
return repository.getErroredDownloads()
|
||||||
}
|
}
|
||||||
|
|
@ -483,10 +518,24 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
return repository.getActiveAndQueuedDownloads()
|
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 {
|
suspend fun queueDownloads(items: List<DownloadItem>) = CoroutineScope(Dispatchers.IO).launch {
|
||||||
val context = App.instance
|
val context = App.instance
|
||||||
val activeAndQueuedDownloads = repository.getActiveAndQueuedDownloads()
|
val activeAndQueuedDownloads = repository.getActiveAndQueuedDownloads()
|
||||||
val allowMeteredNetworks = sharedPreferences.getBoolean("metered_networks", true)
|
|
||||||
val queuedItems = mutableListOf<DownloadItem>()
|
val queuedItems = mutableListOf<DownloadItem>()
|
||||||
var exists = false
|
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)
|
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()
|
val currentTime = System.currentTimeMillis()
|
||||||
var delay = if (it.downloadStartTime != 0L){
|
var delay = 1000L
|
||||||
it.downloadStartTime - currentTime
|
if (queuedItems.isNotEmpty()){
|
||||||
} else 0
|
delay = if (queuedItems[0].downloadStartTime != 0L){
|
||||||
if (delay < 0L) delay = 1L
|
queuedItems[0].downloadStartTime.minus(currentTime)
|
||||||
|
} else 0
|
||||||
|
if (delay <= 0L) delay = 1000L
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
val workConstraints = Constraints.Builder()
|
val workConstraints = Constraints.Builder()
|
||||||
if (!allowMeteredNetworks) workConstraints.setRequiredNetworkType(NetworkType.UNMETERED)
|
if (!allowMeteredNetworks) workConstraints.setRequiredNetworkType(NetworkType.UNMETERED)
|
||||||
|
|
||||||
val workRequest = OneTimeWorkRequestBuilder<DownloadWorker>()
|
val workRequest = OneTimeWorkRequestBuilder<DownloadWorker>()
|
||||||
.setInputData(Data.Builder().putLong("id", it.id).build())
|
|
||||||
.addTag(it.id.toString())
|
|
||||||
.addTag("download")
|
.addTag("download")
|
||||||
.setConstraints(workConstraints.build())
|
.setConstraints(workConstraints.build())
|
||||||
.setInitialDelay(delay, TimeUnit.SECONDS)
|
.setInitialDelay(delay, TimeUnit.MILLISECONDS)
|
||||||
.build()
|
|
||||||
|
if (delay > 1000L){
|
||||||
|
queuedItems.forEach {
|
||||||
|
workRequest.addTag(it.id.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
workManager.enqueueUniqueWork(
|
workManager.enqueueUniqueWork(
|
||||||
it.id.toString(),
|
System.currentTimeMillis().toString(),
|
||||||
ExistingWorkPolicy.REPLACE,
|
ExistingWorkPolicy.REPLACE,
|
||||||
workRequest
|
workRequest.build()
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
val isCurrentNetworkMetered = (context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).isActiveNetworkMetered
|
val isCurrentNetworkMetered = (context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).isActiveNetworkMetered
|
||||||
if (!allowMeteredNetworks && isCurrentNetworkMetered){
|
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{
|
try{
|
||||||
updateDownloadItem(it, infoUtil, dbManager.downloadDao, dbManager.resultDao)
|
updateDownloadItem(it, infoUtil, dbManager.downloadDao, dbManager.resultDao)
|
||||||
}catch (ignored: Exception){}
|
}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(
|
private fun updateDownloadItem(
|
||||||
downloadItem: DownloadItem,
|
downloadItem: DownloadItem,
|
||||||
infoUtil: InfoUtil,
|
infoUtil: InfoUtil,
|
||||||
|
|
|
||||||
|
|
@ -28,10 +28,13 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
|
||||||
val items : LiveData<List<ResultItem>>
|
val items : LiveData<List<ResultItem>>
|
||||||
val loadingItems = MutableLiveData<Boolean>()
|
val loadingItems = MutableLiveData<Boolean>()
|
||||||
private val sharedPreferences: SharedPreferences
|
private val sharedPreferences: SharedPreferences
|
||||||
|
var state: ResultsState = ResultsState.IDLE
|
||||||
|
enum class ResultsState {
|
||||||
|
PROCESSING, IDLE
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val dao = DBManager.getInstance(application).resultDao
|
val dao = DBManager.getInstance(application).resultDao
|
||||||
val commandDao = DBManager.getInstance(application).commandTemplateDao
|
|
||||||
repository = ResultRepository(dao, getApplication<Application>().applicationContext)
|
repository = ResultRepository(dao, getApplication<Application>().applicationContext)
|
||||||
searchHistoryRepository = SearchHistoryRepository(DBManager.getInstance(application).searchHistoryDao)
|
searchHistoryRepository = SearchHistoryRepository(DBManager.getInstance(application).searchHistoryDao)
|
||||||
items = repository.allResults.asLiveData()
|
items = repository.allResults.asLiveData()
|
||||||
|
|
@ -61,6 +64,7 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
suspend fun parseQueries(inputQueries: List<String>){
|
suspend fun parseQueries(inputQueries: List<String>){
|
||||||
|
state = ResultsState.PROCESSING
|
||||||
if (inputQueries.size == 1){
|
if (inputQueries.size == 1){
|
||||||
parseQuery(inputQueries[0], true)
|
parseQuery(inputQueries[0], true)
|
||||||
}else {
|
}else {
|
||||||
|
|
@ -69,6 +73,7 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
|
||||||
inputQueries.forEach {
|
inputQueries.forEach {
|
||||||
parseQuery(it, false)
|
parseQuery(it, false)
|
||||||
}
|
}
|
||||||
|
state = ResultsState.IDLE
|
||||||
loadingItems.postValue(false)
|
loadingItems.postValue(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -97,7 +102,10 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(tag, e.toString())
|
Log.e(tag, e.toString())
|
||||||
}
|
}
|
||||||
if (resetResults) loadingItems.postValue(false)
|
if (resetResults) {
|
||||||
|
state = ResultsState.IDLE
|
||||||
|
loadingItems.postValue(false)
|
||||||
|
}
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -107,7 +115,7 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
|
||||||
val m = p.matcher(inputQuery)
|
val m = p.matcher(inputQuery)
|
||||||
if (m.find()) {
|
if (m.find()) {
|
||||||
type = "YT_Video"
|
type = "YT_Video"
|
||||||
if (inputQuery.contains("playlist?list=")) {
|
if (inputQuery.contains("playlist?list=") && !inputQuery.contains("music.youtu")) {
|
||||||
type = "YT_Playlist"
|
type = "YT_Playlist"
|
||||||
}
|
}
|
||||||
} else if (inputQuery.contains("http")) {
|
} else if (inputQuery.contains("http")) {
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,8 @@ class CancelDownloadNotificationReceiver : BroadcastReceiver() {
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
runCatching {
|
runCatching {
|
||||||
val notificationUtil = NotificationUtil(c)
|
val notificationUtil = NotificationUtil(c)
|
||||||
WorkManager.getInstance(c).cancelUniqueWork(id.toString())
|
|
||||||
notificationUtil.cancelDownloadNotification(id)
|
|
||||||
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
||||||
|
notificationUtil.cancelDownloadNotification(id)
|
||||||
val dbManager = DBManager.getInstance(c)
|
val dbManager = DBManager.getInstance(c)
|
||||||
CoroutineScope(Dispatchers.IO).launch{
|
CoroutineScope(Dispatchers.IO).launch{
|
||||||
runCatching {
|
runCatching {
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,13 @@ package com.deniscerri.ytdlnis.receiver
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
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.deniscerri.ytdlnis.util.NotificationUtil
|
||||||
import com.yausername.youtubedl_android.YoutubeDL
|
import com.yausername.youtubedl_android.YoutubeDL
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class PauseDownloadNotificationReceiver : BroadcastReceiver() {
|
class PauseDownloadNotificationReceiver : BroadcastReceiver() {
|
||||||
override fun onReceive(c: Context, intent: Intent) {
|
override fun onReceive(c: Context, intent: Intent) {
|
||||||
|
|
@ -16,6 +21,14 @@ class PauseDownloadNotificationReceiver : BroadcastReceiver() {
|
||||||
notificationUtil.cancelDownloadNotification(id)
|
notificationUtil.cancelDownloadNotification(id)
|
||||||
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
||||||
notificationUtil.createResumeDownload(id, title, NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID)
|
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.database.viewmodel.ResultViewModel
|
||||||
import com.deniscerri.ytdlnis.ui.BaseActivity
|
import com.deniscerri.ytdlnis.ui.BaseActivity
|
||||||
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
|
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.deniscerri.ytdlnis.util.ThemeUtil
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
|
@ -95,10 +95,9 @@ class ShareActivity : BaseActivity() {
|
||||||
askPermissions()
|
askPermissions()
|
||||||
|
|
||||||
val action = intent.action
|
val action = intent.action
|
||||||
val type = intent.clipData
|
|
||||||
Log.e("aa", intent.toString())
|
Log.e("aa", intent.toString())
|
||||||
if (Intent.ACTION_SEND == action && type != null) {
|
if (Intent.ACTION_SEND == action || Intent.ACTION_VIEW == action) {
|
||||||
if (intent.getStringExtra(Intent.EXTRA_TEXT) == null){
|
if (intent.getStringExtra(Intent.EXTRA_TEXT) == null && Intent.ACTION_SEND == action){
|
||||||
intent.setClass(this, MainActivity::class.java)
|
intent.setClass(this, MainActivity::class.java)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
finishAffinity()
|
finishAffinity()
|
||||||
|
|
@ -107,7 +106,7 @@ class ShareActivity : BaseActivity() {
|
||||||
|
|
||||||
runCatching { supportFragmentManager.popBackStack() }
|
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)
|
val loadingBottomSheet = BottomSheetDialog(this)
|
||||||
loadingBottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
loadingBottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||||
|
|
@ -118,7 +117,11 @@ class ShareActivity : BaseActivity() {
|
||||||
this.finish()
|
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 {
|
lifecycleScope.launch {
|
||||||
try {
|
try {
|
||||||
val result = resultViewModel.getItemByURL(inputQuery)
|
val result = resultViewModel.getItemByURL(inputQuery)
|
||||||
|
|
@ -166,7 +169,7 @@ class ShareActivity : BaseActivity() {
|
||||||
|
|
||||||
private fun showSelectPlaylistItems(it: List<ResultItem?>){
|
private fun showSelectPlaylistItems(it: List<ResultItem?>){
|
||||||
if (sharedPreferences.getBoolean("download_card", true)){
|
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")
|
bottomSheet.show(supportFragmentManager, "downloadPlaylistSheet")
|
||||||
}else{
|
}else{
|
||||||
lifecycleScope.launch(Dispatchers.IO){
|
lifecycleScope.launch(Dispatchers.IO){
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import android.content.*
|
||||||
import android.content.Context.CLIPBOARD_SERVICE
|
import android.content.Context.CLIPBOARD_SERVICE
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
|
import android.graphics.drawable.ShapeDrawable
|
||||||
|
import android.graphics.drawable.shapes.OvalShape
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
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.ui.downloadcard.ResultCardDetailsDialog
|
||||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
import com.deniscerri.ytdlnis.util.ThemeUtil
|
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.facebook.shimmer.ShimmerFrameLayout
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import com.google.android.material.appbar.MaterialToolbar
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
|
|
@ -55,6 +59,7 @@ import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import me.zhanghai.android.fastscroll.FastScrollerBuilder
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -146,6 +151,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
|
||||||
recyclerView = view.findViewById(R.id.recyclerViewHome)
|
recyclerView = view.findViewById(R.id.recyclerViewHome)
|
||||||
recyclerView?.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
|
recyclerView?.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
|
||||||
recyclerView?.adapter = homeAdapter
|
recyclerView?.adapter = homeAdapter
|
||||||
|
recyclerView?.enableFastScroll()
|
||||||
|
|
||||||
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
||||||
resultViewModel.items.observe(viewLifecycleOwner) {
|
resultViewModel.items.observe(viewLifecycleOwner) {
|
||||||
|
|
@ -251,12 +257,15 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
if(arguments?.getString("url") == null){
|
if(arguments?.getString("url") == null){
|
||||||
resultViewModel.checkTrending()
|
if (resultViewModel.state == ResultViewModel.ResultsState.IDLE){
|
||||||
|
resultViewModel.checkTrending()
|
||||||
|
}else{
|
||||||
|
resultViewModel.loadingItems.postValue(true)
|
||||||
|
}
|
||||||
}else{
|
}else{
|
||||||
arguments
|
|
||||||
arguments?.remove("url")
|
arguments?.remove("url")
|
||||||
arguments?.remove("showDownloadsWithUpdatedFormats")
|
|
||||||
}
|
}
|
||||||
|
arguments?.remove("showDownloadsWithUpdatedFormats")
|
||||||
if (searchView?.currentTransitionState == SearchView.TransitionState.SHOWN){
|
if (searchView?.currentTransitionState == SearchView.TransitionState.SHOWN){
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
updateSearchViewItems(searchView?.editText?.text, linkYouCopied)
|
updateSearchViewItems(searchView?.editText?.text, linkYouCopied)
|
||||||
|
|
@ -533,7 +542,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
|
||||||
resultViewModel.deleteAll()
|
resultViewModel.deleteAll()
|
||||||
lifecycleScope.launch(Dispatchers.IO){
|
lifecycleScope.launch(Dispatchers.IO){
|
||||||
Thread.sleep(300)
|
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())){
|
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())
|
val resultItem = downloadViewModel.createEmptyResultItem(queryList.first())
|
||||||
if (sharedPreferences!!.getBoolean("download_card", true)) {
|
if (sharedPreferences!!.getBoolean("download_card", true)) {
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ import java.util.*
|
||||||
import java.util.regex.Pattern
|
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 infoUtil: InfoUtil
|
||||||
private lateinit var sharedPreferences: SharedPreferences
|
private lateinit var sharedPreferences: SharedPreferences
|
||||||
private lateinit var commandTemplateViewModel: CommandTemplateViewModel
|
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 text = view.findViewById<EditText>(R.id.command)
|
||||||
val templates = view.findViewById<Button>(R.id.commands)
|
val templates = view.findViewById<Button>(R.id.commands)
|
||||||
val shortcuts = view.findViewById<Button>(R.id.shortcuts)
|
val shortcuts = view.findViewById<Button>(R.id.shortcuts)
|
||||||
val currentCommand = infoUtil.parseYTDLRequestString(infoUtil.buildYoutubeDLRequest(item))
|
|
||||||
val currentText = view.findViewById<TextView>(R.id.currentText)
|
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
|
//init syntax highlighter
|
||||||
|
|
@ -111,7 +116,10 @@ class AddExtraCommandsDialog(private val item: DownloadItem, private val callbac
|
||||||
highlight.setSpan(currentText)
|
highlight.setSpan(currentText)
|
||||||
currentText.addTextChangedListener(highlightWatcher)
|
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
|
val imm = activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
text!!.postDelayed({
|
text!!.postDelayed({
|
||||||
text.setSelection(text.length())
|
text.setSelection(text.length())
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package com.deniscerri.ytdlnis.ui.downloadcard
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
|
import android.content.SharedPreferences
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
|
@ -15,6 +16,7 @@ import android.view.View
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.core.view.children
|
import androidx.core.view.children
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.media3.common.MediaItem.fromUri
|
import androidx.media3.common.MediaItem.fromUri
|
||||||
import androidx.media3.common.Player
|
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.MediaSource
|
||||||
import androidx.media3.exoplayer.source.MergingMediaSource
|
import androidx.media3.exoplayer.source.MergingMediaSource
|
||||||
import androidx.media3.ui.PlayerView
|
import androidx.media3.ui.PlayerView
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.ChapterItem
|
import com.deniscerri.ytdlnis.database.models.ChapterItem
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
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.chip.ChipGroup
|
||||||
import com.google.android.material.color.MaterialColors
|
import com.google.android.material.color.MaterialColors
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
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.slider.RangeSlider
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import com.google.gson.Gson
|
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 toTextInput : TextInputLayout
|
||||||
private lateinit var cancelBtn : Button
|
private lateinit var cancelBtn : Button
|
||||||
private lateinit var okBtn : Button
|
private lateinit var okBtn : Button
|
||||||
|
private lateinit var forceKeyframes: MaterialSwitch
|
||||||
private lateinit var suggestedChips: ChipGroup
|
private lateinit var suggestedChips: ChipGroup
|
||||||
private lateinit var suggestedChapters : LinearLayout
|
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)
|
newCutBtn = view.findViewById(R.id.new_cut)
|
||||||
resetBtn = view.findViewById(R.id.reset_all)
|
resetBtn = view.findViewById(R.id.reset_all)
|
||||||
chipGroup = view.findViewById(R.id.cut_list_chip_group)
|
chipGroup = view.findViewById(R.id.cut_list_chip_group)
|
||||||
|
forceKeyframes = view.findViewById(R.id.force_keyframes)
|
||||||
|
|
||||||
|
|
||||||
selectedCuts = if (chipGroup.childCount == 0){
|
selectedCuts = if (chipGroup.childCount == 0){
|
||||||
|
|
@ -152,7 +158,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
||||||
if (urls.isNullOrEmpty()) {
|
if (urls.isNullOrEmpty()) {
|
||||||
infoUtil.getStreamingUrlAndChapters(item.url)
|
infoUtil.getStreamingUrlAndChapters(item.url)
|
||||||
}else {
|
}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) {}
|
}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")
|
@SuppressLint("SetTextI18n")
|
||||||
|
|
@ -253,31 +268,25 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
||||||
fromTextInput.editText!!.setTextAndRecalculateWidth("0:00")
|
fromTextInput.editText!!.setTextAndRecalculateWidth("0:00")
|
||||||
toTextInput.editText!!.setTextAndRecalculateWidth(item.duration)
|
toTextInput.editText!!.setTextAndRecalculateWidth(item.duration)
|
||||||
|
|
||||||
rangeSlider.addOnSliderTouchListener(object : RangeSlider.OnSliderTouchListener {
|
rangeSlider.setOnTouchListener { v, event -> // Handle touch events here
|
||||||
override fun onStartTrackingTouch(slider: RangeSlider) {
|
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) {
|
rangeSlider.setOnDragListener { view, dragEvent ->
|
||||||
val values = rangeSlider.values
|
updateFromSlider()
|
||||||
val startTimestamp = (values[0].toInt() * timeSeconds) / 100
|
false
|
||||||
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) {}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
fromTextInput.editText!!.setOnKeyListener(object : View.OnKeyListener {
|
fromTextInput.editText!!.setOnKeyListener(object : View.OnKeyListener {
|
||||||
|
|
||||||
|
|
@ -304,8 +313,10 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
||||||
startValue = 0F
|
startValue = 0F
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fromTextInput.editText!!.setTextAndRecalculateWidth(fromTextInput.editText!!.text.toString())
|
||||||
|
|
||||||
rangeSlider.setValues(startValue, endValue)
|
rangeSlider.setValues(startValue, endValue)
|
||||||
okBtn.isEnabled = startValue != 0F && endValue != 100F
|
okBtn.isEnabled = startValue != 0F || endValue != 100F
|
||||||
try {
|
try {
|
||||||
player.seekTo(seconds.toLong() * 1000)
|
player.seekTo(seconds.toLong() * 1000)
|
||||||
player.play()
|
player.play()
|
||||||
|
|
@ -341,12 +352,14 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
||||||
|
|
||||||
if (seconds == 0) {
|
if (seconds == 0) {
|
||||||
toTextInput.editText!!.setTextAndRecalculateWidth(infoUtil.formatIntegerDuration(endTimestamp, Locale.US))
|
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(infoUtil.formatIntegerDuration(endTimestamp, Locale.US))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toTextInput.editText!!.setTextAndRecalculateWidth(toTextInput.editText!!.text.toString())
|
||||||
|
|
||||||
rangeSlider.setValues(startValue, endValue)
|
rangeSlider.setValues(startValue, endValue)
|
||||||
okBtn.isEnabled = startValue != 0F && endValue != 100F
|
okBtn.isEnabled = startValue != 0F || endValue != 100F
|
||||||
try {
|
try {
|
||||||
player.seekTo(startSeconds.toLong() * 1000)
|
player.seekTo(startSeconds.toLong() * 1000)
|
||||||
player.play()
|
player.play()
|
||||||
|
|
@ -370,6 +383,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
||||||
|
|
||||||
okBtn.isEnabled = false
|
okBtn.isEnabled = false
|
||||||
okBtn.setOnClickListener {
|
okBtn.setOnClickListener {
|
||||||
|
forceKeyframes.isVisible = true
|
||||||
val chip = createChip("${fromTextInput.editText!!.text}-${toTextInput.editText!!.text}")
|
val chip = createChip("${fromTextInput.editText!!.text}-${toTextInput.editText!!.text}")
|
||||||
chip.performClick()
|
chip.performClick()
|
||||||
cutSection.visibility = View.GONE
|
cutSection.visibility = View.GONE
|
||||||
|
|
@ -379,6 +393,26 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
||||||
populateSuggestedChapters()
|
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(){
|
private fun populateSuggestedChapters(){
|
||||||
if (chapters!!.isEmpty()) suggestedChapters.visibility = View.GONE
|
if (chapters!!.isEmpty()) suggestedChapters.visibility = View.GONE
|
||||||
else {
|
else {
|
||||||
|
|
@ -391,6 +425,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
||||||
chip.isCheckedIconVisible = false
|
chip.isCheckedIconVisible = false
|
||||||
suggestedChips.addView(chip)
|
suggestedChips.addView(chip)
|
||||||
chip.setOnClickListener { c ->
|
chip.setOnClickListener { c ->
|
||||||
|
forceKeyframes.isVisible = true
|
||||||
val createdChip = createChapterChip(it, null)
|
val createdChip = createChapterChip(it, null)
|
||||||
createdChip.performClick()
|
createdChip.performClick()
|
||||||
cutSection.visibility = View.GONE
|
cutSection.visibility = View.GONE
|
||||||
|
|
@ -424,6 +459,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.downloadSections.isNotBlank()){
|
if (item.downloadSections.isNotBlank()){
|
||||||
|
forceKeyframes.isVisible = true
|
||||||
chipGroup.removeAllViews()
|
chipGroup.removeAllViews()
|
||||||
item.downloadSections.split(";").forEachIndexed { _, it ->
|
item.downloadSections.split(";").forEachIndexed { _, it ->
|
||||||
if (it.isBlank()) return
|
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 = ""
|
if (containers[index] == getString(R.string.defaultValue)) downloadItem.container = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
val embedThumb = view.findViewById<Chip>(R.id.embed_thumb)
|
UiUtil.configureAudio(
|
||||||
embedThumb!!.isChecked = downloadItem.audioPreferences.embedThumb
|
view,
|
||||||
embedThumb.setOnClickListener {
|
requireActivity(),
|
||||||
downloadItem.audioPreferences.embedThumb = embedThumb.isChecked
|
listOf(downloadItem),
|
||||||
}
|
embedThumbClicked = {
|
||||||
|
downloadItem.audioPreferences.embedThumb = it
|
||||||
val splitByChapters = view.findViewById<Chip>(R.id.split_by_chapters)
|
},
|
||||||
if (downloadItem.downloadSections.isNotBlank()){
|
splitByChaptersClicked = {
|
||||||
splitByChapters.isEnabled = false
|
downloadItem.audioPreferences.splitByChapters = it
|
||||||
splitByChapters.isChecked = false
|
},
|
||||||
}else{
|
filenameTemplateSet = {
|
||||||
splitByChapters!!.isChecked = downloadItem.audioPreferences.splitByChapters
|
downloadItem.customFileNameTemplate = it
|
||||||
}
|
},
|
||||||
|
sponsorBlockItemsSet = { values, checkedItems ->
|
||||||
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 ->
|
|
||||||
downloadItem.audioPreferences.sponsorBlockFilters.clear()
|
downloadItem.audioPreferences.sponsorBlockFilters.clear()
|
||||||
for (i in 0 until checkedItems.size) {
|
for (i in 0 until checkedItems.size) {
|
||||||
if (checkedItems[i]) {
|
if (checkedItems[i]) {
|
||||||
downloadItem.audioPreferences.sponsorBlockFilters.add(values[i])
|
downloadItem.audioPreferences.sponsorBlockFilters.add(values[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
cutClicked = {cutVideoListener ->
|
||||||
// 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 {
|
|
||||||
if (parentFragmentManager.findFragmentByTag("cutVideoSheet") == null){
|
if (parentFragmentManager.findFragmentByTag("cutVideoSheet") == null){
|
||||||
val bottomSheet = CutVideoBottomSheetDialog(downloadItem, resultItem.urls, resultItem.chapters, cutVideoListener)
|
val bottomSheet = CutVideoBottomSheetDialog(downloadItem, resultItem.urls, resultItem.chapters, cutVideoListener)
|
||||||
bottomSheet.show(parentFragmentManager, "cutVideoSheet")
|
bottomSheet.show(parentFragmentManager, "cutVideoSheet")
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
extraCommandsClicked = {
|
||||||
}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 {
|
|
||||||
val callback = object : ExtraCommandsListener {
|
val callback = object : ExtraCommandsListener {
|
||||||
override fun onChangeExtraCommand(c: String) {
|
override fun onChangeExtraCommand(c: String) {
|
||||||
downloadItem.extraCommands = c
|
downloadItem.extraCommands = c
|
||||||
|
|
@ -358,9 +249,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
|
||||||
val bottomSheetDialog = AddExtraCommandsDialog(downloadItem, callback)
|
val bottomSheetDialog = AddExtraCommandsDialog(downloadItem, callback)
|
||||||
bottomSheetDialog.show(parentFragmentManager, "extraCommands")
|
bottomSheetDialog.show(parentFragmentManager, "extraCommands")
|
||||||
}
|
}
|
||||||
}else{
|
)
|
||||||
extraCommands.visibility = View.GONE
|
|
||||||
}
|
|
||||||
}catch (e : Exception){
|
}catch (e : Exception){
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import android.annotation.SuppressLint
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.format.DateFormat
|
import android.text.format.DateFormat
|
||||||
import android.util.DisplayMetrics
|
import android.util.DisplayMetrics
|
||||||
|
|
@ -73,8 +74,9 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
|
||||||
behavior = BottomSheetBehavior.from(view.parent as View)
|
behavior = BottomSheetBehavior.from(view.parent as View)
|
||||||
val displayMetrics = DisplayMetrics()
|
val displayMetrics = DisplayMetrics()
|
||||||
requireActivity().windowManager.defaultDisplay.getMetrics(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.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
|
File(FileUtil.formatPath(downloadItem.downloadPath)).freeSpace
|
||||||
))
|
))
|
||||||
|
|
||||||
val newTemplate : Chip = view.findViewById(R.id.newTemplate)
|
UiUtil.configureCommand(
|
||||||
newTemplate.setOnClickListener {
|
view,
|
||||||
UiUtil.showCommandTemplateCreationOrUpdatingSheet(null, requireActivity(), viewLifecycleOwner, commandTemplateViewModel) {
|
1,
|
||||||
templates.add(it)
|
newTemplateClicked = {
|
||||||
chosenCommandView.editText!!.setText(it.content)
|
UiUtil.showCommandTemplateCreationOrUpdatingSheet(null, requireActivity(), viewLifecycleOwner, commandTemplateViewModel) {
|
||||||
downloadItem.format = Format(
|
templates.add(it)
|
||||||
it.title,
|
chosenCommandView.editText!!.setText(it.content)
|
||||||
"",
|
downloadItem.format = Format(
|
||||||
"",
|
it.title,
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
0,
|
"",
|
||||||
it.content
|
"",
|
||||||
)
|
0,
|
||||||
populateCommandTemplates(templates, autoCompleteTextView)
|
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) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import android.app.Dialog
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
|
@ -39,6 +40,7 @@ import androidx.work.OneTimeWorkRequestBuilder
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.adapter.ConfigureMultipleDownloadsAdapter
|
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.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.models.Format
|
import com.deniscerri.ytdlnis.database.models.Format
|
||||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
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.receiver.ShareActivity
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
|
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
|
||||||
import com.deniscerri.ytdlnis.work.UpdatePlaylistFormatsWorker
|
import com.deniscerri.ytdlnis.work.UpdatePlaylistFormatsWorker
|
||||||
import com.google.android.material.bottomappbar.BottomAppBar
|
import com.google.android.material.bottomappbar.BottomAppBar
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
|
|
@ -63,6 +66,7 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.lang.reflect.Type
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
|
|
@ -98,8 +102,9 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
|
||||||
behavior = BottomSheetBehavior.from(view.parent as View)
|
behavior = BottomSheetBehavior.from(view.parent as View)
|
||||||
val displayMetrics = DisplayMetrics()
|
val displayMetrics = DisplayMetrics()
|
||||||
requireActivity().windowManager.defaultDisplay.getMetrics(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.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 = view.findViewById(R.id.downloadMultipleRecyclerview)
|
||||||
recyclerView.layoutManager = LinearLayoutManager(requireContext())
|
recyclerView.layoutManager = LinearLayoutManager(requireContext())
|
||||||
recyclerView.adapter = listAdapter
|
recyclerView.adapter = listAdapter
|
||||||
val itemTouchHelper = ItemTouchHelper(simpleCallback)
|
recyclerView.enableFastScroll()
|
||||||
itemTouchHelper.attachToRecyclerView(recyclerView)
|
if(sharedPreferences.getBoolean("swipe_gestures", true)){
|
||||||
|
val itemTouchHelper = ItemTouchHelper(simpleCallback)
|
||||||
|
itemTouchHelper.attachToRecyclerView(recyclerView)
|
||||||
|
}
|
||||||
listAdapter.submitList(items.toList())
|
listAdapter.submitList(items.toList())
|
||||||
|
|
||||||
val scheduleBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_schedule_button)
|
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 ->
|
bottomAppBar.setOnMenuItemClickListener { m: MenuItem ->
|
||||||
when (m.itemId) {
|
when (m.itemId) {
|
||||||
R.id.folder -> {
|
R.id.folder -> {
|
||||||
|
|
@ -264,6 +274,7 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
|
||||||
listAdapter.notifyDataSetChanged()
|
listAdapter.notifyDataSetChanged()
|
||||||
preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24)
|
preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24)
|
||||||
bottomAppBar.menu[1].icon?.alpha = 255
|
bottomAppBar.menu[1].icon?.alpha = 255
|
||||||
|
bottomAppBar.menu[3].icon?.alpha = 255
|
||||||
bottomSheet.cancel()
|
bottomSheet.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -273,6 +284,7 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
|
||||||
listAdapter.notifyDataSetChanged()
|
listAdapter.notifyDataSetChanged()
|
||||||
preferredDownloadType.setIcon(R.drawable.baseline_video_file_24)
|
preferredDownloadType.setIcon(R.drawable.baseline_video_file_24)
|
||||||
bottomAppBar.menu[1].icon?.alpha = 255
|
bottomAppBar.menu[1].icon?.alpha = 255
|
||||||
|
bottomAppBar.menu[3].icon?.alpha = 255
|
||||||
bottomSheet.cancel()
|
bottomSheet.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -285,6 +297,7 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
|
||||||
listAdapter.notifyDataSetChanged()
|
listAdapter.notifyDataSetChanged()
|
||||||
preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24)
|
preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24)
|
||||||
bottomAppBar.menu[1].icon?.alpha = 255
|
bottomAppBar.menu[1].icon?.alpha = 255
|
||||||
|
bottomAppBar.menu[3].icon?.alpha = 30
|
||||||
bottomSheet.cancel()
|
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 -> {
|
R.id.format -> {
|
||||||
if (! items.all { it.type == items[0].type }){
|
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
|
true
|
||||||
}
|
}
|
||||||
|
|
@ -507,8 +604,12 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
|
||||||
items[i] = item
|
items[i] = item
|
||||||
if (! items.all { it.type == items[0].type }){
|
if (! items.all { it.type == items[0].type }){
|
||||||
bottomAppBar.menu[1].icon?.alpha = 30
|
bottomAppBar.menu[1].icon?.alpha = 30
|
||||||
|
bottomAppBar.menu[3].icon?.alpha = 30
|
||||||
}else{
|
}else{
|
||||||
bottomAppBar.menu[1].icon?.alpha = 255
|
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){
|
if (items.count { it.type == DownloadViewModel.Type.command } == 0){
|
||||||
|
|
@ -581,7 +682,7 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
|
||||||
super.onChildDraw(
|
super.onChildDraw(
|
||||||
c,
|
c,
|
||||||
recyclerView,
|
recyclerView,
|
||||||
viewHolder!!,
|
viewHolder,
|
||||||
dX,
|
dX,
|
||||||
dY,
|
dY,
|
||||||
actionState,
|
actionState,
|
||||||
|
|
|
||||||
|
|
@ -221,193 +221,51 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
val embedSubs = view.findViewById<Chip>(R.id.embed_subtitles)
|
UiUtil.configureVideo(
|
||||||
embedSubs!!.isChecked = downloadItem.videoPreferences.embedSubs
|
view,
|
||||||
embedSubs.setOnClickListener {
|
requireActivity(),
|
||||||
downloadItem.videoPreferences.embedSubs = embedSubs.isChecked
|
listOf(downloadItem),
|
||||||
}
|
embedSubsClicked = {
|
||||||
|
downloadItem.videoPreferences.embedSubs = it
|
||||||
val addChapters = view.findViewById<Chip>(R.id.add_chapters)
|
},
|
||||||
addChapters!!.isChecked = downloadItem.videoPreferences.addChapters
|
addChaptersClicked = {
|
||||||
addChapters.setOnClickListener{
|
downloadItem.videoPreferences.addChapters = it
|
||||||
downloadItem.videoPreferences.addChapters = addChapters.isChecked
|
},
|
||||||
}
|
splitByChaptersClicked = {
|
||||||
|
downloadItem.videoPreferences.splitByChapters = it
|
||||||
|
},
|
||||||
val splitByChapters = view.findViewById<Chip>(R.id.split_by_chapters)
|
saveThumbnailClicked = {
|
||||||
if(downloadItem.downloadSections.isNotBlank()){
|
downloadItem.SaveThumb = it
|
||||||
splitByChapters.isEnabled = false
|
},
|
||||||
splitByChapters.isChecked = false
|
sponsorBlockItemsSet = { values, checkedItems ->
|
||||||
}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 ->
|
|
||||||
downloadItem.videoPreferences.sponsorBlockFilters.clear()
|
downloadItem.videoPreferences.sponsorBlockFilters.clear()
|
||||||
for (i in 0 until checkedItems.size) {
|
for (i in checkedItems.indices) {
|
||||||
if (checkedItems[i]) {
|
if (checkedItems[i]) {
|
||||||
downloadItem.videoPreferences.sponsorBlockFilters.add(values[i])
|
downloadItem.videoPreferences.sponsorBlockFilters.add(values[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
cutClicked = { cutVideoListener ->
|
||||||
// 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 {
|
|
||||||
if (parentFragmentManager.findFragmentByTag("cutVideoSheet") == null){
|
if (parentFragmentManager.findFragmentByTag("cutVideoSheet") == null){
|
||||||
val bottomSheet = CutVideoBottomSheetDialog(downloadItem, resultItem.urls, resultItem.chapters, cutVideoListener)
|
val bottomSheet = CutVideoBottomSheetDialog(downloadItem, resultItem.urls, resultItem.chapters, cutVideoListener)
|
||||||
bottomSheet.show(parentFragmentManager, "cutVideoSheet")
|
bottomSheet.show(parentFragmentManager, "cutVideoSheet")
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}else{
|
filenameTemplateSet = {
|
||||||
cut.isEnabled = false
|
downloadItem.customFileNameTemplate = it
|
||||||
}
|
},
|
||||||
|
saveSubtitlesClicked = {
|
||||||
val filenameTemplate = view.findViewById<Chip>(R.id.filename_template)
|
downloadItem.videoPreferences.writeSubs = it
|
||||||
filenameTemplate.setOnClickListener {
|
},
|
||||||
val builder = MaterialAlertDialogBuilder(requireContext())
|
subtitleLanguagesClicked = {
|
||||||
builder.setTitle(getString(R.string.file_name_template))
|
UiUtil.showSubtitleLanguagesDialog(requireActivity(), downloadItem.videoPreferences.subsLanguages){
|
||||||
val inputLayout = layoutInflater.inflate(R.layout.textinput, null)
|
downloadItem.videoPreferences.subsLanguages = it
|
||||||
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)
|
removeAudioClicked = {
|
||||||
editText.setSelection(editText.text.length)
|
downloadItem.videoPreferences.removeAudio = it
|
||||||
builder.setView(inputLayout)
|
},
|
||||||
builder.setPositiveButton(
|
extraCommandsClicked = {
|
||||||
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 {
|
|
||||||
val callback = object : ExtraCommandsListener {
|
val callback = object : ExtraCommandsListener {
|
||||||
override fun onChangeExtraCommand(c: String) {
|
override fun onChangeExtraCommand(c: String) {
|
||||||
downloadItem.extraCommands = c
|
downloadItem.extraCommands = c
|
||||||
|
|
@ -417,9 +275,7 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
|
||||||
val bottomSheetDialog = AddExtraCommandsDialog(downloadItem, callback)
|
val bottomSheetDialog = AddExtraCommandsDialog(downloadItem, callback)
|
||||||
bottomSheetDialog.show(parentFragmentManager, "extraCommands")
|
bottomSheetDialog.show(parentFragmentManager, "extraCommands")
|
||||||
}
|
}
|
||||||
}else{
|
)
|
||||||
extraCommands.visibility = View.GONE
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
||||||
private lateinit var view: View
|
private lateinit var view: View
|
||||||
|
|
||||||
enum class FormatSorting {
|
enum class FormatSorting {
|
||||||
filesize, container, id
|
filesize, container, codec, id
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
|
@ -127,7 +127,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
||||||
refreshBtn.setOnClickListener {
|
refreshBtn.setOnClickListener {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
if (items.size > 10){
|
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) {
|
continueInBackgroundSnackBar.setAction(R.string.ok) {
|
||||||
listener.onContinueOnBackground(items)
|
listener.onContinueOnBackground(items)
|
||||||
this@FormatSelectionBottomSheetDialog.dismiss()
|
this@FormatSelectionBottomSheetDialog.dismiss()
|
||||||
|
|
@ -158,7 +158,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
||||||
|
|
||||||
formats = listOf(res)
|
formats = listOf(res)
|
||||||
|
|
||||||
//playlist format filtering
|
//list format filtering
|
||||||
}else{
|
}else{
|
||||||
var progress = "0/${items.size}"
|
var progress = "0/${items.size}"
|
||||||
formatCollection.clear()
|
formatCollection.clear()
|
||||||
|
|
@ -168,8 +168,8 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
||||||
lifecycleScope.launch(Dispatchers.Main){
|
lifecycleScope.launch(Dispatchers.Main){
|
||||||
progress = "${formatCollection.size}/${items.size}"
|
progress = "${formatCollection.size}/${items.size}"
|
||||||
refreshBtn.text = progress
|
refreshBtn.text = progress
|
||||||
|
formatCollection.add(it)
|
||||||
}
|
}
|
||||||
formatCollection.add(it)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
formats = formatCollection
|
formats = formatCollection
|
||||||
|
|
@ -242,6 +242,14 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
||||||
val finalFormats: List<Format> = when(sortBy){
|
val finalFormats: List<Format> = when(sortBy){
|
||||||
FormatSorting.container -> chosenFormats.groupBy { it.container }.flatMap { it.value }
|
FormatSorting.container -> chosenFormats.groupBy { it.container }.flatMap { it.value }
|
||||||
FormatSorting.id -> chosenFormats.sortedBy { it.format_id }
|
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
|
FormatSorting.filesize -> chosenFormats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import android.app.DownloadManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
|
@ -28,6 +29,7 @@ import androidx.media3.exoplayer.source.MediaSource
|
||||||
import androidx.media3.exoplayer.source.MergingMediaSource
|
import androidx.media3.exoplayer.source.MergingMediaSource
|
||||||
import androidx.media3.ui.PlayerView
|
import androidx.media3.ui.PlayerView
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import androidx.paging.filter
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
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.ActiveDownloadAdapter
|
||||||
import com.deniscerri.ytdlnis.adapter.ActiveDownloadMinifiedAdapter
|
import com.deniscerri.ytdlnis.adapter.ActiveDownloadMinifiedAdapter
|
||||||
import com.deniscerri.ytdlnis.adapter.GenericDownloadAdapter
|
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.models.ResultItem
|
||||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
|
|
@ -47,6 +48,7 @@ import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.deniscerri.ytdlnis.util.VideoPlayerUtil
|
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.BottomSheetDialog
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||||
import com.google.android.material.button.MaterialButton
|
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 com.yausername.youtubedl_android.YoutubeDL
|
||||||
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.text.FieldPosition
|
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
@ -73,15 +76,14 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
|
||||||
private lateinit var downloadViewModel: DownloadViewModel
|
private lateinit var downloadViewModel: DownloadViewModel
|
||||||
private lateinit var resultViewModel: ResultViewModel
|
private lateinit var resultViewModel: ResultViewModel
|
||||||
|
|
||||||
private lateinit var activeDownloads: ActiveDownloadMinifiedAdapter
|
private lateinit var activeAdapter: ActiveDownloadMinifiedAdapter
|
||||||
private lateinit var queuedDownloads: GenericDownloadAdapter
|
private lateinit var queuedAdapter: GenericDownloadAdapter
|
||||||
private var activeCount: Int = 0
|
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 downloadManager: DownloadManager
|
||||||
|
|
||||||
private lateinit var sharedPreferences: SharedPreferences
|
private lateinit var sharedPreferences: SharedPreferences
|
||||||
|
private lateinit var dialogView : View
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
@ -89,8 +91,6 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
|
||||||
notificationUtil = NotificationUtil(requireActivity().applicationContext)
|
notificationUtil = NotificationUtil(requireActivity().applicationContext)
|
||||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||||
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
||||||
queuedItems = listOf()
|
|
||||||
activeItems = listOf()
|
|
||||||
downloadManager = requireContext().getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
downloadManager = requireContext().getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
}
|
}
|
||||||
|
|
@ -101,7 +101,8 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View {
|
): View {
|
||||||
// Inflate the layout to use as dialog or embedded fragment
|
// 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 {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
|
@ -116,14 +117,27 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
|
||||||
super.setupDialog(dialog, style)
|
super.setupDialog(dialog, style)
|
||||||
val view = LayoutInflater.from(context).inflate(R.layout.result_card_details, null)
|
val view = LayoutInflater.from(context).inflate(R.layout.result_card_details, null)
|
||||||
dialog.setContentView(view)
|
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)
|
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
activeDownloads = ActiveDownloadMinifiedAdapter(this,requireActivity())
|
activeAdapter = ActiveDownloadMinifiedAdapter(this,requireActivity())
|
||||||
queuedDownloads = GenericDownloadAdapter(this,requireActivity())
|
queuedAdapter = GenericDownloadAdapter(this,requireActivity())
|
||||||
|
|
||||||
val bottomSheetLink = view.findViewById<MaterialButton>(R.id.bottom_sheet_link)
|
val bottomSheetLink = view.findViewById<MaterialButton>(R.id.bottom_sheet_link)
|
||||||
val downloadThumb = view.findViewById<MaterialButton>(R.id.download_thumb)
|
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 queuedRecycler = view.findViewById<RecyclerView>(R.id.queued_recycler)
|
||||||
val queued = view.findViewById<TextView>(R.id.queued)
|
val queued = view.findViewById<TextView>(R.id.queued)
|
||||||
|
|
||||||
runningRecycler.adapter = activeDownloads
|
runningRecycler.adapter = activeAdapter
|
||||||
runningRecycler.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
|
runningRecycler.layoutManager = GridLayoutManager(context, 1)
|
||||||
|
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
if (preferences.getBoolean("swipe_gestures", true)){
|
if (preferences.getBoolean("swipe_gestures", true)){
|
||||||
|
|
@ -146,56 +160,57 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
|
||||||
itemTouchHelper.attachToRecyclerView(queuedRecycler)
|
itemTouchHelper.attachToRecyclerView(queuedRecycler)
|
||||||
}
|
}
|
||||||
|
|
||||||
queuedRecycler.adapter = queuedDownloads
|
queuedRecycler.adapter = queuedAdapter
|
||||||
queuedRecycler.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
|
queuedRecycler.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
|
||||||
|
|
||||||
downloadViewModel.activeDownloads.map { it.filter { d -> d.url == item.url } }.observe(viewLifecycleOwner) {
|
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
|
running.visibility = View.GONE
|
||||||
runningRecycler.visibility = View.GONE
|
runningRecycler.visibility = View.GONE
|
||||||
}else{
|
}else{
|
||||||
running.visibility = View.VISIBLE
|
running.visibility = View.VISIBLE
|
||||||
runningRecycler.visibility = View.VISIBLE
|
runningRecycler.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
it.forEach{item ->
|
WorkManager.getInstance(requireContext())
|
||||||
WorkManager.getInstance(requireContext())
|
.getWorkInfosByTagLiveData("download")
|
||||||
.getWorkInfosForUniqueWorkLiveData(item.id.toString())
|
.observe(viewLifecycleOwner){ list ->
|
||||||
.observe(viewLifecycleOwner){ list ->
|
list.forEach {work ->
|
||||||
list.forEach {work ->
|
if (work == null) return@forEach
|
||||||
if (work == null) return@observe
|
val id = work.progress.getLong("id", 0L)
|
||||||
val id = work.progress.getLong("id", 0L)
|
if(id == 0L) return@forEach
|
||||||
if(id == 0L) return@observe
|
|
||||||
|
|
||||||
val progress = work.progress.getInt("progress", 0)
|
val progress = work.progress.getInt("progress", 0)
|
||||||
|
val progressBar = view.findViewWithTag<LinearProgressIndicator>("$id##progress")
|
||||||
val progressBar = view.findViewWithTag<LinearProgressIndicator>("$id##progress")
|
requireActivity().runOnUiThread {
|
||||||
|
try {
|
||||||
requireActivity().runOnUiThread {
|
progressBar?.setProgressCompat(progress, true)
|
||||||
try {
|
}catch (ignored: Exception) {}
|
||||||
progressBar?.setProgressCompat(progress, true)
|
}
|
||||||
}catch (ignored: Exception) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
activeItems = it
|
lifecycleScope.launch {
|
||||||
activeDownloads.submitList(it)
|
downloadViewModel.queuedDownloads.map { it.filter { d -> d.url == item.url } }.collectLatest {
|
||||||
|
queuedAdapter.submitData(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
queuedAdapter.addLoadStateListener { loadState ->
|
||||||
downloadViewModel.queuedDownloads.map { it.filter { d -> d.url == item.url } }.observe(viewLifecycleOwner) {
|
lifecycleScope.launch {
|
||||||
if (it.isEmpty()) {
|
if (loadState.append.endOfPaginationReached )
|
||||||
queued.visibility = View.GONE
|
{
|
||||||
queuedRecycler.visibility = View.GONE
|
if (queuedAdapter.itemCount < 1){
|
||||||
}else{
|
queued.visibility = View.GONE
|
||||||
queued.visibility = View.VISIBLE
|
queuedRecycler.visibility = View.GONE
|
||||||
queuedRecycler.visibility = View.VISIBLE
|
}else{
|
||||||
|
queued.visibility = View.VISIBLE
|
||||||
|
queuedRecycler.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
queuedItems = it
|
|
||||||
queuedDownloads.submitList(it)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadViewModel.activeDownloadsCount.observe(viewLifecycleOwner){
|
downloadViewModel.activeDownloadsCount.observe(viewLifecycleOwner){
|
||||||
|
|
@ -238,23 +253,9 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
|
||||||
|
|
||||||
|
|
||||||
videoView = view.findViewById(R.id.video_view)
|
videoView = view.findViewById(R.id.video_view)
|
||||||
val pause = view.findViewById<MaterialButton>(R.id.pause)
|
|
||||||
|
|
||||||
val player = VideoPlayerUtil.buildPlayer(requireContext())
|
val player = VideoPlayerUtil.buildPlayer(requireContext())
|
||||||
videoView.player = player
|
videoView.player = player
|
||||||
|
|
||||||
videoView.setOnClickListener {
|
|
||||||
if (player.isPlaying){
|
|
||||||
player.pause()
|
|
||||||
pause.visibility = View.VISIBLE
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
player.play()
|
|
||||||
pause.visibility = View.GONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
try {
|
try {
|
||||||
val data : MutableList<String?> = withContext(Dispatchers.IO){
|
val data : MutableList<String?> = withContext(Dispatchers.IO){
|
||||||
|
|
@ -324,25 +325,29 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeQueuedItem(id: Long){
|
private fun removeQueuedItem(id: Long){
|
||||||
val item = queuedItems.find { it.id == id } ?: return
|
lifecycleScope.launch {
|
||||||
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
|
val item = withContext(Dispatchers.IO){
|
||||||
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + item.title + "\"!")
|
downloadViewModel.getItemByID(id)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
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)
|
Snackbar.make(requireView().rootView, getString(R.string.cancelled) + ": " + item.title, Snackbar.LENGTH_LONG)
|
||||||
.setAction(getString(R.string.undo)) {
|
.setAction(getString(R.string.undo)) {
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
downloadViewModel.deleteDownload(item)
|
downloadViewModel.deleteDownload(item.id)
|
||||||
downloadViewModel.queueDownloads(listOf(item))
|
downloadViewModel.queueDownloads(listOf(item))
|
||||||
}
|
}
|
||||||
}.show()
|
}.show()
|
||||||
|
}
|
||||||
|
deleteDialog.show()
|
||||||
}
|
}
|
||||||
deleteDialog.show()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var simpleCallback: ItemTouchHelper.SimpleCallback =
|
private var simpleCallback: ItemTouchHelper.SimpleCallback =
|
||||||
|
|
@ -353,12 +358,16 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||||
val position = viewHolder.bindingAdapterPosition
|
val itemID = viewHolder.itemView.tag.toString().toLong()
|
||||||
when (direction) {
|
when (direction) {
|
||||||
ItemTouchHelper.LEFT -> {
|
ItemTouchHelper.LEFT -> {
|
||||||
val deletedItem = queuedItems[position]
|
lifecycleScope.launch {
|
||||||
queuedDownloads.notifyItemChanged(position)
|
val deletedItem = withContext(Dispatchers.IO){
|
||||||
removeQueuedItem(deletedItem.id)
|
downloadViewModel.getItemByID(itemID)
|
||||||
|
}
|
||||||
|
queuedAdapter.notifyItemChanged(viewHolder.bindingAdapterPosition)
|
||||||
|
removeQueuedItem(deletedItem.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -396,7 +405,7 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
|
||||||
super.onChildDraw(
|
super.onChildDraw(
|
||||||
c,
|
c,
|
||||||
recyclerView,
|
recyclerView,
|
||||||
viewHolder!!,
|
viewHolder,
|
||||||
dX,
|
dX,
|
||||||
dY,
|
dY,
|
||||||
actionState,
|
actionState,
|
||||||
|
|
@ -410,133 +419,135 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCardClick(itemID: Long) {
|
override fun onCardClick(itemID: Long) {
|
||||||
val item = queuedItems.find { it.id == itemID } ?: return
|
lifecycleScope.launch {
|
||||||
|
val item = withContext(Dispatchers.IO){
|
||||||
val bottomSheet = BottomSheetDialog(requireContext())
|
downloadViewModel.getItemByID(itemID)
|
||||||
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)
|
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)
|
if (item.format.format_note == "?" || item.format.format_note == "") formatNote!!.visibility =
|
||||||
val formatNote = bottomSheet.findViewById<Chip>(R.id.format_note)
|
View.GONE
|
||||||
val container = bottomSheet.findViewById<Chip>(R.id.container_chip)
|
else formatNote!!.text = item.format.format_note
|
||||||
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
|
if (item.format.container != "") container!!.text = item.format.container.uppercase()
|
||||||
else {
|
else container!!.visibility = View.GONE
|
||||||
val calendar = Calendar.getInstance()
|
|
||||||
calendar.timeInMillis = item.downloadStartTime
|
|
||||||
time!!.text = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(calendar.time)
|
|
||||||
|
|
||||||
time.setOnClickListener {
|
val codecText =
|
||||||
UiUtil.showDatePicker(parentFragmentManager) {
|
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()
|
bottomSheet.dismiss()
|
||||||
Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + it.time, Toast.LENGTH_LONG).show()
|
downloadViewModel.deleteDownload(item.id)
|
||||||
downloadViewModel.deleteDownload(item)
|
item.downloadStartTime = 0
|
||||||
item.downloadStartTime = it.timeInMillis
|
|
||||||
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(item.id.toString())
|
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(item.id.toString())
|
||||||
runBlocking {
|
runBlocking {
|
||||||
downloadViewModel.queueDownloads(listOf(item))
|
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) {
|
override fun onCancelClick(itemID: Long) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
|
|
@ -557,41 +568,45 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
|
||||||
cancelActiveDownload(itemID)
|
cancelActiveDownload(itemID)
|
||||||
}
|
}
|
||||||
override fun onPauseClick(itemID: Long, action: ActiveDownloadAdapter.ActiveDownloadAction, position: Int) {
|
override fun onPauseClick(itemID: Long, action: ActiveDownloadAdapter.ActiveDownloadAction, position: Int) {
|
||||||
val item = activeItems.find { it.id == itemID } ?: return
|
lifecycleScope.launch {
|
||||||
when(action){
|
val item = withContext(Dispatchers.IO){
|
||||||
ActiveDownloadAdapter.ActiveDownloadAction.Pause -> {
|
downloadViewModel.getItemByID(itemID)
|
||||||
lifecycleScope.launch {
|
|
||||||
cancelItem(itemID.toInt())
|
|
||||||
item.status = DownloadRepository.Status.Paused.toString()
|
|
||||||
withContext(Dispatchers.IO){
|
|
||||||
downloadViewModel.updateDownload(item)
|
|
||||||
}
|
|
||||||
activeDownloads.notifyItemChanged(position)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ActiveDownloadAdapter.ActiveDownloadAction.Resume -> {
|
when(action){
|
||||||
lifecycleScope.launch {
|
ActiveDownloadAdapter.ActiveDownloadAction.Pause -> {
|
||||||
item.status = DownloadRepository.Status.Active.toString()
|
lifecycleScope.launch {
|
||||||
withContext(Dispatchers.IO){
|
cancelItem(itemID.toInt())
|
||||||
downloadViewModel.updateDownload(item)
|
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)
|
val queue = if (activeCount > 1) listOf(item)
|
||||||
else withContext(Dispatchers.IO){
|
else withContext(Dispatchers.IO){
|
||||||
val list = downloadViewModel.getQueued().toMutableList()
|
val list = downloadViewModel.getQueued().toMutableList()
|
||||||
list.map { it.status = DownloadRepository.Status.Queued.toString() }
|
list.map { it.status = DownloadRepository.Status.Queued.toString() }
|
||||||
list.add(0, item)
|
list.add(0, item)
|
||||||
list
|
list
|
||||||
}
|
}
|
||||||
|
|
||||||
runBlocking {
|
runBlocking {
|
||||||
downloadViewModel.queueDownloads(queue)
|
downloadViewModel.queueDownloads(queue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
override fun onCardClick() {
|
override fun onCardClick() {
|
||||||
this.dismiss()
|
this.dismiss()
|
||||||
|
|
@ -607,15 +622,19 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cancelActiveDownload(itemID: Long){
|
private fun cancelActiveDownload(itemID: Long){
|
||||||
val id = itemID.toInt()
|
lifecycleScope.launch {
|
||||||
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
val id = itemID.toInt()
|
||||||
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(id.toString())
|
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
||||||
notificationUtil.cancelDownloadNotification(id)
|
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(id.toString())
|
||||||
|
notificationUtil.cancelDownloadNotification(id)
|
||||||
|
|
||||||
activeItems.find { it.id == itemID }?.let {
|
withContext(Dispatchers.IO){
|
||||||
it.status = DownloadRepository.Status.Cancelled.toString()
|
downloadViewModel.getItemByID(itemID)
|
||||||
lifecycleScope.launch(Dispatchers.IO){
|
}.let {
|
||||||
downloadViewModel.updateDownload(it)
|
it.status = DownloadRepository.Status.Cancelled.toString()
|
||||||
|
lifecycleScope.launch(Dispatchers.IO){
|
||||||
|
downloadViewModel.updateDownload(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.deniscerri.ytdlnis.ui.downloadcard
|
package com.deniscerri.ytdlnis.ui.downloadcard
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.ActionBar.LayoutParams
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
|
@ -8,9 +9,12 @@ import android.util.DisplayMetrics
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.Window
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.core.widget.doAfterTextChanged
|
import androidx.core.widget.doAfterTextChanged
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
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.DownloadViewModel
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
||||||
import com.deniscerri.ytdlnis.receiver.ShareActivity
|
import com.deniscerri.ytdlnis.receiver.ShareActivity
|
||||||
|
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil.setTextAndRecalculateWidth
|
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.bottomappbar.BottomAppBar
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
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.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.*
|
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 downloadViewModel: DownloadViewModel
|
||||||
private lateinit var resultViewModel: ResultViewModel
|
private lateinit var resultViewModel: ResultViewModel
|
||||||
private lateinit var listAdapter : PlaylistAdapter
|
private lateinit var listAdapter : PlaylistAdapter
|
||||||
private lateinit var recyclerView: RecyclerView
|
private lateinit var recyclerView: RecyclerView
|
||||||
private lateinit var behavior: BottomSheetBehavior<View>
|
private lateinit var ok: MenuItem
|
||||||
private lateinit var selectedText: TextView
|
private lateinit var toolbar: MaterialToolbar
|
||||||
private lateinit var fromTextInput: TextInputLayout
|
private lateinit var fromTextInput: TextInputLayout
|
||||||
private lateinit var toTextInput: TextInputLayout
|
private lateinit var toTextInput: TextInputLayout
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
setStyle(STYLE_NORMAL, R.style.FullScreenDialogTheme)
|
||||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||||
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("RestrictedApi")
|
override fun onCreateView(
|
||||||
override fun setupDialog(dialog: Dialog, style: Int) {
|
inflater: LayoutInflater,
|
||||||
super.setupDialog(dialog, style)
|
container: ViewGroup?,
|
||||||
val view = LayoutInflater.from(context).inflate(R.layout.select_playlist_items, null)
|
savedInstanceState: Bundle?
|
||||||
dialog.setContentView(view)
|
): View? {
|
||||||
|
return inflater.inflate(R.layout.select_playlist_items, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
dialog.setOnShowListener {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
behavior = BottomSheetBehavior.from(view.parent as View)
|
val dialog = super.onCreateDialog(savedInstanceState)
|
||||||
val displayMetrics = DisplayMetrics()
|
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||||
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
return dialog
|
||||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
|
||||||
|
dialog?.window?.setLayout(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
||||||
|
|
||||||
|
toolbar = view.findViewById<MaterialToolbar>(R.id.toolbar)
|
||||||
|
|
||||||
listAdapter =
|
listAdapter =
|
||||||
PlaylistAdapter(
|
PlaylistAdapter(
|
||||||
|
|
@ -70,10 +86,10 @@ class SelectPlaylistItemsBottomSheetDialog(private val items: List<ResultItem?>,
|
||||||
recyclerView = view.findViewById(R.id.downloadMultipleRecyclerview)
|
recyclerView = view.findViewById(R.id.downloadMultipleRecyclerview)
|
||||||
recyclerView.layoutManager = LinearLayoutManager(requireContext())
|
recyclerView.layoutManager = LinearLayoutManager(requireContext())
|
||||||
recyclerView.adapter = listAdapter
|
recyclerView.adapter = listAdapter
|
||||||
|
recyclerView.enableFastScroll()
|
||||||
listAdapter.submitList(items)
|
listAdapter.submitList(items)
|
||||||
|
|
||||||
selectedText = view.findViewById<Button>(R.id.selected)
|
toolbar.title = "0 ${resources.getString(R.string.selected)}"
|
||||||
selectedText.text = "0 ${resources.getString(R.string.selected)}"
|
|
||||||
|
|
||||||
fromTextInput = view.findViewById(R.id.from_textinput)
|
fromTextInput = view.findViewById(R.id.from_textinput)
|
||||||
toTextInput = view.findViewById(R.id.to_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 (startNr <= 0) startNr = 0
|
||||||
if (endNr > items.size) endNr = items.size - 1
|
if (endNr > items.size) endNr = items.size - 1
|
||||||
listAdapter.checkRange(startNr, endNr)
|
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 ->
|
toTextInput.editText!!.doAfterTextChanged { _text ->
|
||||||
reset()
|
reset()
|
||||||
|
|
||||||
val start = fromTextInput.editText!!.text.toString()
|
val start = fromTextInput.editText!!.text.toString()
|
||||||
val end = _text.toString()
|
val end = _text.toString()
|
||||||
|
|
||||||
|
|
@ -113,7 +129,8 @@ class SelectPlaylistItemsBottomSheetDialog(private val items: List<ResultItem?>,
|
||||||
if (startNr <= 0) startNr = 0
|
if (startNr <= 0) startNr = 0
|
||||||
if (endNr > items.size) endNr = items.size -1
|
if (endNr > items.size) endNr = items.size -1
|
||||||
listAdapter.checkRange(startNr, endNr)
|
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()
|
listAdapter.checkAll()
|
||||||
fromTextInput.isEnabled = true
|
fromTextInput.isEnabled = true
|
||||||
toTextInput.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{
|
}else{
|
||||||
reset()
|
reset()
|
||||||
fromTextInput.isEnabled = true
|
fromTextInput.isEnabled = true
|
||||||
toTextInput.isEnabled = true
|
toTextInput.isEnabled = true
|
||||||
|
ok.isEnabled = false
|
||||||
fromTextInput.editText!!.setTextAndRecalculateWidth("")
|
fromTextInput.editText!!.setTextAndRecalculateWidth("")
|
||||||
toTextInput.editText!!.setTextAndRecalculateWidth("")
|
toTextInput.editText!!.setTextAndRecalculateWidth("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
val ok = view.findViewById<Button>(R.id.bottomsheet_ok)
|
ok = toolbar.menu.getItem(0)
|
||||||
ok!!.setOnClickListener {
|
ok.isEnabled = false
|
||||||
|
ok.setOnMenuItemClickListener {
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
val checkedItems = listAdapter.getCheckedItems()
|
val checkedItems = listAdapter.getCheckedItems()
|
||||||
val checkedResultItems = items.filter { item -> checkedItems.contains(item!!.url) }
|
val checkedResultItems = items.filter { item -> checkedItems.contains(item!!.url) }
|
||||||
|
|
@ -164,6 +184,7 @@ class SelectPlaylistItemsBottomSheetDialog(private val items: List<ResultItem?>,
|
||||||
dismiss()
|
dismiss()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
view.findViewById<BottomAppBar>(R.id.bottomAppBar).setOnMenuItemClickListener { m: MenuItem ->
|
view.findViewById<BottomAppBar>(R.id.bottomAppBar).setOnMenuItemClickListener { m: MenuItem ->
|
||||||
|
|
@ -172,17 +193,22 @@ class SelectPlaylistItemsBottomSheetDialog(private val items: List<ResultItem?>,
|
||||||
listAdapter.invertSelected(items)
|
listAdapter.invertSelected(items)
|
||||||
val checkedItems = listAdapter.getCheckedItems()
|
val checkedItems = listAdapter.getCheckedItems()
|
||||||
if (checkedItems.size == items.size){
|
if (checkedItems.size == items.size){
|
||||||
selectedText.text = resources.getString(R.string.all_items_selected)
|
toolbar.title= resources.getString(R.string.all_items_selected)
|
||||||
}else{
|
}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){
|
if(checkedItems.isNotEmpty() && checkedItems.size < items.size){
|
||||||
fromTextInput.isEnabled = false
|
fromTextInput.isEnabled = false
|
||||||
toTextInput.isEnabled = false
|
toTextInput.isEnabled = false
|
||||||
}
|
}
|
||||||
|
ok.isEnabled = checkedItems.isNotEmpty()
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toolbar.setNavigationOnClickListener {
|
||||||
|
requireActivity().finishAffinity()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCancel(dialog: DialogInterface) {
|
override fun onCancel(dialog: DialogInterface) {
|
||||||
|
|
@ -209,21 +235,24 @@ class SelectPlaylistItemsBottomSheetDialog(private val items: List<ResultItem?>,
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun reset(){
|
private fun reset(){
|
||||||
|
ok.isEnabled = false
|
||||||
listAdapter.clearCheckeditems()
|
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>) {
|
override fun onCardSelect(itemURL: String, isChecked: Boolean, checkedItems: List<String>) {
|
||||||
if (checkedItems.size == items.size){
|
if (checkedItems.size == items.size){
|
||||||
selectedText.text = resources.getString(R.string.all_items_selected)
|
toolbar.title = resources.getString(R.string.all_items_selected)
|
||||||
}else{
|
}else{
|
||||||
selectedText.text = "${checkedItems.size} ${resources.getString(R.string.selected)}"
|
toolbar.title = "${checkedItems.size} ${resources.getString(R.string.selected)}"
|
||||||
}
|
}
|
||||||
if (checkedItems.isEmpty()){
|
if (checkedItems.isEmpty()){
|
||||||
|
ok.isEnabled = false
|
||||||
fromTextInput.isEnabled = true
|
fromTextInput.isEnabled = true
|
||||||
toTextInput.isEnabled = true
|
toTextInput.isEnabled = true
|
||||||
}else{
|
}else{
|
||||||
|
ok.isEnabled = true
|
||||||
fromTextInput.isEnabled = false
|
fromTextInput.isEnabled = false
|
||||||
toTextInput.isEnabled = false
|
toTextInput.isEnabled = false
|
||||||
}
|
}
|
||||||
|
|
@ -10,9 +10,11 @@ import android.widget.TextView
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.map
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.work.WorkInfo
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.adapter.ActiveDownloadAdapter
|
import com.deniscerri.ytdlnis.adapter.ActiveDownloadAdapter
|
||||||
|
|
@ -39,7 +41,6 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
||||||
private lateinit var activeDownloads : ActiveDownloadAdapter
|
private lateinit var activeDownloads : ActiveDownloadAdapter
|
||||||
lateinit var downloadItem: DownloadItem
|
lateinit var downloadItem: DownloadItem
|
||||||
private lateinit var notificationUtil: NotificationUtil
|
private lateinit var notificationUtil: NotificationUtil
|
||||||
private lateinit var list: List<DownloadItem>
|
|
||||||
private lateinit var pauseResume: MaterialButton
|
private lateinit var pauseResume: MaterialButton
|
||||||
private lateinit var workManager: WorkManager
|
private lateinit var workManager: WorkManager
|
||||||
|
|
||||||
|
|
@ -53,7 +54,6 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
||||||
activity = getActivity()
|
activity = getActivity()
|
||||||
notificationUtil = NotificationUtil(requireContext())
|
notificationUtil = NotificationUtil(requireContext())
|
||||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||||
list = listOf()
|
|
||||||
workManager = WorkManager.getInstance(requireContext())
|
workManager = WorkManager.getInstance(requireContext())
|
||||||
return fragmentView
|
return fragmentView
|
||||||
}
|
}
|
||||||
|
|
@ -77,6 +77,7 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
||||||
if (pauseResume.text == requireContext().getString(R.string.pause)){
|
if (pauseResume.text == requireContext().getString(R.string.pause)){
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
|
pauseResume.isEnabled = false
|
||||||
val queued = withContext(Dispatchers.IO){
|
val queued = withContext(Dispatchers.IO){
|
||||||
downloadViewModel.getQueued()
|
downloadViewModel.getQueued()
|
||||||
}
|
}
|
||||||
|
|
@ -92,18 +93,28 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
||||||
workManager.cancelAllWorkByTag(it.id.toString())
|
workManager.cancelAllWorkByTag(it.id.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
list.forEach {
|
val active = withContext(Dispatchers.IO){
|
||||||
|
downloadViewModel.getActiveDownloads()
|
||||||
|
}
|
||||||
|
|
||||||
|
active.forEach {
|
||||||
cancelItem(it.id.toInt())
|
cancelItem(it.id.toInt())
|
||||||
it.status = DownloadRepository.Status.Paused.toString()
|
it.status = DownloadRepository.Status.Paused.toString()
|
||||||
downloadViewModel.updateDownload(it)
|
downloadViewModel.updateDownload(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
activeDownloads.notifyDataSetChanged()
|
activeDownloads.notifyDataSetChanged()
|
||||||
|
pauseResume.isEnabled = true
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
lifecycleScope.launch {
|
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 {
|
toQueue.forEach {
|
||||||
it.status = DownloadRepository.Status.Active.toString()
|
it.status = DownloadRepository.Status.Active.toString()
|
||||||
downloadViewModel.updateDownload(it)
|
downloadViewModel.updateDownload(it)
|
||||||
|
|
@ -116,17 +127,40 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
||||||
runBlocking {
|
runBlocking {
|
||||||
downloadViewModel.queueDownloads(toQueue)
|
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) {
|
downloadViewModel.activeDownloads.observe(viewLifecycleOwner) {
|
||||||
list = it
|
|
||||||
activeDownloads.submitList(it)
|
activeDownloads.submitList(it)
|
||||||
|
|
||||||
if (it.size > 1){
|
if (it.size > 1){
|
||||||
pauseResume.visibility = View.VISIBLE
|
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)
|
pauseResume.text = requireContext().getString(R.string.resume)
|
||||||
}else{
|
}else{
|
||||||
pauseResume.text = requireContext().getString(R.string.pause)
|
pauseResume.text = requireContext().getString(R.string.pause)
|
||||||
|
|
@ -135,37 +169,16 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
||||||
activeDownloads.notifyDataSetChanged()
|
activeDownloads.notifyDataSetChanged()
|
||||||
pauseResume.visibility = View.GONE
|
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) {
|
override fun onCancelClick(itemID: Long) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
if (list.size == 1){
|
val count = withContext(Dispatchers.IO){
|
||||||
|
downloadViewModel.getActiveDownloads()
|
||||||
|
}.count()
|
||||||
|
|
||||||
|
if (count == 1){
|
||||||
val queue = withContext(Dispatchers.IO){
|
val queue = withContext(Dispatchers.IO){
|
||||||
val list = downloadViewModel.getQueued().toMutableList()
|
val list = downloadViewModel.getQueued().toMutableList()
|
||||||
list.map { it.status = DownloadRepository.Status.Queued.toString() }
|
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) {
|
override fun onPauseClick(itemID: Long, action: ActiveDownloadAdapter.ActiveDownloadAction, position: Int) {
|
||||||
val item = list.find { it.id == itemID } ?: return
|
lifecycleScope.launch {
|
||||||
when(action){
|
val item = withContext(Dispatchers.IO){
|
||||||
ActiveDownloadAdapter.ActiveDownloadAction.Pause -> {
|
downloadViewModel.getItemByID(itemID)
|
||||||
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)
|
|
||||||
|
|
||||||
val queue = if (list.size > 1) listOf(item)
|
when(action){
|
||||||
else withContext(Dispatchers.IO){
|
ActiveDownloadAdapter.ActiveDownloadAction.Pause -> {
|
||||||
val list = downloadViewModel.getQueued().toMutableList()
|
lifecycleScope.launch {
|
||||||
list.map { it.status = DownloadRepository.Status.Queued.toString() }
|
cancelItem(itemID.toInt())
|
||||||
list.add(0, item)
|
item.status = DownloadRepository.Status.Paused.toString()
|
||||||
list
|
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 {
|
val count = withContext(Dispatchers.IO){
|
||||||
downloadViewModel.queueDownloads(queue)
|
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) {
|
override fun onOutputClick(item: DownloadItem) {
|
||||||
|
|
@ -238,17 +260,19 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cancelDownload(itemID: Long){
|
private fun cancelDownload(itemID: Long){
|
||||||
cancelItem(itemID.toInt())
|
lifecycleScope.launch {
|
||||||
list.find { it.id == itemID }?.let {
|
cancelItem(itemID.toInt())
|
||||||
it.status = DownloadRepository.Status.Cancelled.toString()
|
withContext(Dispatchers.IO){
|
||||||
lifecycleScope.launch(Dispatchers.IO){
|
downloadViewModel.getItemByID(itemID)
|
||||||
downloadViewModel.updateDownload(it)
|
}.let {
|
||||||
|
it.status = DownloadRepository.Status.Cancelled.toString()
|
||||||
|
withContext(Dispatchers.IO){
|
||||||
|
downloadViewModel.updateDownload(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(p0: View?) {
|
override fun onClick(p0: View?) {
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,28 +1,21 @@
|
||||||
package com.deniscerri.ytdlnis.ui.downloads
|
package com.deniscerri.ytdlnis.ui.downloads
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.DisplayMetrics
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.Window
|
|
||||||
import android.widget.Button
|
|
||||||
import android.widget.TextView
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.view.ActionMode
|
import androidx.appcompat.view.ActionMode
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
|
|
@ -30,20 +23,22 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.adapter.GenericDownloadAdapter
|
import com.deniscerri.ytdlnis.adapter.GenericDownloadAdapter
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding
|
|
||||||
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
|
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
|
||||||
|
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil.forceFastScrollMode
|
import com.deniscerri.ytdlnis.util.UiUtil.forceFastScrollMode
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
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.color.MaterialColors
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
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.runBlocking
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
|
||||||
class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickListener {
|
class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickListener {
|
||||||
|
|
@ -51,10 +46,9 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
|
||||||
private var activity: Activity? = null
|
private var activity: Activity? = null
|
||||||
private lateinit var downloadViewModel : DownloadViewModel
|
private lateinit var downloadViewModel : DownloadViewModel
|
||||||
private lateinit var cancelledRecyclerView : RecyclerView
|
private lateinit var cancelledRecyclerView : RecyclerView
|
||||||
private lateinit var cancelledDownloads : GenericDownloadAdapter
|
private lateinit var adapter : GenericDownloadAdapter
|
||||||
private var selectedObjects: ArrayList<DownloadItem>? = null
|
|
||||||
private var actionMode : ActionMode? = null
|
private var actionMode : ActionMode? = null
|
||||||
private lateinit var items : MutableList<DownloadItem>
|
private var totalSize = 0
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
|
|
@ -63,16 +57,14 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
|
||||||
): View? {
|
): View? {
|
||||||
fragmentView = inflater.inflate(R.layout.fragment_generic_download_queue, container, false)
|
fragmentView = inflater.inflate(R.layout.fragment_generic_download_queue, container, false)
|
||||||
activity = getActivity()
|
activity = getActivity()
|
||||||
selectedObjects = arrayListOf()
|
|
||||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||||
items = mutableListOf()
|
|
||||||
return fragmentView
|
return fragmentView
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
cancelledDownloads =
|
adapter =
|
||||||
GenericDownloadAdapter(
|
GenericDownloadAdapter(
|
||||||
this,
|
this,
|
||||||
requireActivity()
|
requireActivity()
|
||||||
|
|
@ -80,7 +72,8 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
|
||||||
|
|
||||||
cancelledRecyclerView = view.findViewById(R.id.download_recyclerview)
|
cancelledRecyclerView = view.findViewById(R.id.download_recyclerview)
|
||||||
cancelledRecyclerView.forceFastScrollMode()
|
cancelledRecyclerView.forceFastScrollMode()
|
||||||
cancelledRecyclerView.adapter = cancelledDownloads
|
cancelledRecyclerView.adapter = adapter
|
||||||
|
cancelledRecyclerView.enableFastScroll()
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
if (preferences.getBoolean("swipe_gestures", true)){
|
if (preferences.getBoolean("swipe_gestures", true)){
|
||||||
val itemTouchHelper = ItemTouchHelper(simpleCallback)
|
val itemTouchHelper = ItemTouchHelper(simpleCallback)
|
||||||
|
|
@ -89,146 +82,74 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
|
||||||
cancelledRecyclerView.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
|
cancelledRecyclerView.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
|
||||||
|
|
||||||
|
|
||||||
downloadViewModel.cancelledDownloads.observe(viewLifecycleOwner) {
|
lifecycleScope.launch {
|
||||||
items = it.toMutableList()
|
downloadViewModel.cancelledDownloads.collectLatest {
|
||||||
cancelledDownloads.submitList(it)
|
adapter.submitData(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadViewModel.getTotalSize(listOf(DownloadRepository.Status.Cancelled)).observe(viewLifecycleOwner){
|
||||||
|
totalSize = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActionButtonClick(itemID: Long) {
|
override fun onActionButtonClick(itemID: Long) {
|
||||||
val item = items.find { it.id == itemID }
|
lifecycleScope.launch {
|
||||||
if (item != null){
|
runCatching {
|
||||||
runBlocking {
|
val item = withContext(Dispatchers.IO){
|
||||||
downloadViewModel.queueDownloads(listOf(item))
|
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) {
|
override fun onCardClick(itemID: Long) {
|
||||||
val item = items.find { it.id == itemID } ?: return
|
lifecycleScope.launch {
|
||||||
|
val item = withContext(Dispatchers.IO){
|
||||||
val bottomSheet = BottomSheetDialog(requireContext())
|
downloadViewModel.getItemByID(itemID)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
override fun onCardSelect(isChecked: Boolean, position: Int) {
|
||||||
val item = items.find { it.id == itemID }
|
lifecycleScope.launch {
|
||||||
if (isChecked) {
|
val selectedObjects = adapter.getSelectedObjectsCount(totalSize)
|
||||||
selectedObjects!!.add(item!!)
|
if (isChecked) {
|
||||||
if (actionMode == null){
|
if (actionMode == null){
|
||||||
actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
|
actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
|
||||||
|
}else{
|
||||||
}else{
|
actionMode!!.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||||
actionMode!!.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
}
|
||||||
}
|
}
|
||||||
}
|
else {
|
||||||
else {
|
actionMode?.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||||
selectedObjects!!.remove(item)
|
if (selectedObjects == 0){
|
||||||
actionMode?.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
actionMode?.finish()
|
||||||
if (selectedObjects!!.isEmpty()){
|
}
|
||||||
actionMode?.finish()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -240,7 +161,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
|
||||||
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + item.title + "\"!")
|
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + item.title + "\"!")
|
||||||
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||||
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||||
downloadViewModel.deleteDownload(item)
|
downloadViewModel.deleteDownload(item.id)
|
||||||
}
|
}
|
||||||
deleteDialog.show()
|
deleteDialog.show()
|
||||||
}
|
}
|
||||||
|
|
@ -249,7 +170,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
|
||||||
private val contextualActionBar = object : ActionMode.Callback {
|
private val contextualActionBar = object : ActionMode.Callback {
|
||||||
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||||
mode!!.menuInflater.inflate(R.menu.cancelled_downloads_menu_context, menu)
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -270,39 +191,56 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
|
||||||
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items))
|
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items))
|
||||||
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||||
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||||
for (obj in selectedObjects!!){
|
lifecycleScope.launch {
|
||||||
downloadViewModel.deleteDownload(obj)
|
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()
|
deleteDialog.show()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.redownload -> {
|
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 {
|
runBlocking {
|
||||||
downloadViewModel.queueDownloads(selectedObjects!!.toMutableList())
|
|
||||||
actionMode?.finish()
|
actionMode?.finish()
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.select_all -> {
|
R.id.select_all -> {
|
||||||
cancelledDownloads.checkAll(items)
|
adapter.checkAll()
|
||||||
selectedObjects?.clear()
|
|
||||||
items.forEach { selectedObjects?.add(it) }
|
|
||||||
mode?.title = getString(R.string.all_items_selected)
|
mode?.title = getString(R.string.all_items_selected)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.invert_selected -> {
|
R.id.invert_selected -> {
|
||||||
cancelledDownloads.invertSelected(items)
|
adapter.invertSelected()
|
||||||
val invertedList = arrayListOf<DownloadItem>()
|
val selectedObjects = adapter.getSelectedObjectsCount(totalSize)
|
||||||
items.forEach {
|
actionMode!!.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||||
if (!selectedObjects?.contains(it)!!) invertedList.add(it)
|
if (selectedObjects == 0) actionMode?.finish()
|
||||||
}
|
|
||||||
selectedObjects?.clear()
|
|
||||||
selectedObjects?.addAll(invertedList)
|
|
||||||
actionMode!!.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
|
||||||
if (invertedList.isEmpty()) actionMode?.finish()
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
else -> false
|
else -> false
|
||||||
|
|
@ -311,15 +249,10 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
|
||||||
|
|
||||||
override fun onDestroyActionMode(mode: ActionMode?) {
|
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||||
actionMode = null
|
actionMode = null
|
||||||
clearCheckedItems()
|
adapter.clearCheckedItems()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun clearCheckedItems(){
|
|
||||||
cancelledDownloads.clearCheckeditems()
|
|
||||||
selectedObjects?.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
private var simpleCallback: ItemTouchHelper.SimpleCallback =
|
private var simpleCallback: ItemTouchHelper.SimpleCallback =
|
||||||
object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
|
object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
|
||||||
override fun onMove(recyclerView: RecyclerView,viewHolder: RecyclerView.ViewHolder,target: RecyclerView.ViewHolder
|
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) {
|
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||||
val position = viewHolder.bindingAdapterPosition
|
val itemID = viewHolder.itemView.tag.toString().toLong()
|
||||||
when (direction) {
|
when (direction) {
|
||||||
ItemTouchHelper.LEFT -> {
|
ItemTouchHelper.LEFT -> {
|
||||||
val deletedItem = items[position]
|
lifecycleScope.launch {
|
||||||
downloadViewModel.deleteDownload(deletedItem)
|
val deletedItem = withContext(Dispatchers.IO){
|
||||||
Snackbar.make(cancelledRecyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
|
downloadViewModel.getItemByID(itemID)
|
||||||
.setAction(getString(R.string.undo)) {
|
}
|
||||||
downloadViewModel.insert(deletedItem)
|
downloadViewModel.deleteDownload(deletedItem.id)
|
||||||
}.show()
|
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 -> {
|
ItemTouchHelper.RIGHT -> {
|
||||||
onActionButtonClick(items[position].id)
|
onActionButtonClick(itemID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,10 @@ class DownloadQueueMainFragment : Fragment(){
|
||||||
initMenu()
|
initMenu()
|
||||||
|
|
||||||
if (arguments?.getString("tab") != null){
|
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
|
package com.deniscerri.ytdlnis.ui.downloads
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.DisplayMetrics
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.Window
|
|
||||||
import android.widget.AdapterView
|
import android.widget.AdapterView
|
||||||
import android.widget.AdapterView.OnItemClickListener
|
import android.widget.AdapterView.OnItemClickListener
|
||||||
import android.widget.Button
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.view.ActionMode
|
import androidx.appcompat.view.ActionMode
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
|
@ -31,19 +25,22 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.adapter.GenericDownloadAdapter
|
import com.deniscerri.ytdlnis.adapter.GenericDownloadAdapter
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
|
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
|
||||||
|
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil.forceFastScrollMode
|
import com.deniscerri.ytdlnis.util.UiUtil.forceFastScrollMode
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
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.color.MaterialColors
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
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.runBlocking
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
|
||||||
class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickListener, OnItemClickListener {
|
class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickListener, OnItemClickListener {
|
||||||
|
|
@ -51,10 +48,9 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
|
||||||
private var activity: Activity? = null
|
private var activity: Activity? = null
|
||||||
private lateinit var downloadViewModel : DownloadViewModel
|
private lateinit var downloadViewModel : DownloadViewModel
|
||||||
private lateinit var erroredRecyclerView : RecyclerView
|
private lateinit var erroredRecyclerView : RecyclerView
|
||||||
private lateinit var erroredDownloads : GenericDownloadAdapter
|
private lateinit var adapter : GenericDownloadAdapter
|
||||||
private lateinit var items : MutableList<DownloadItem>
|
|
||||||
private var selectedObjects: ArrayList<DownloadItem>? = null
|
|
||||||
private var actionMode : ActionMode? = null
|
private var actionMode : ActionMode? = null
|
||||||
|
private var totalSize: Int = 0
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
|
|
@ -63,15 +59,13 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
|
||||||
fragmentView = inflater.inflate(R.layout.fragment_generic_download_queue, container, false)
|
fragmentView = inflater.inflate(R.layout.fragment_generic_download_queue, container, false)
|
||||||
activity = getActivity()
|
activity = getActivity()
|
||||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||||
items = mutableListOf()
|
|
||||||
selectedObjects = arrayListOf()
|
|
||||||
return fragmentView
|
return fragmentView
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
erroredDownloads =
|
adapter =
|
||||||
GenericDownloadAdapter(
|
GenericDownloadAdapter(
|
||||||
this,
|
this,
|
||||||
requireActivity()
|
requireActivity()
|
||||||
|
|
@ -79,7 +73,8 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
|
||||||
|
|
||||||
erroredRecyclerView = view.findViewById(R.id.download_recyclerview)
|
erroredRecyclerView = view.findViewById(R.id.download_recyclerview)
|
||||||
erroredRecyclerView.forceFastScrollMode()
|
erroredRecyclerView.forceFastScrollMode()
|
||||||
erroredRecyclerView.adapter = erroredDownloads
|
erroredRecyclerView.adapter = adapter
|
||||||
|
erroredRecyclerView.enableFastScroll()
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
if (preferences.getBoolean("swipe_gestures", true)){
|
if (preferences.getBoolean("swipe_gestures", true)){
|
||||||
val itemTouchHelper = ItemTouchHelper(simpleCallback)
|
val itemTouchHelper = ItemTouchHelper(simpleCallback)
|
||||||
|
|
@ -87,147 +82,77 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
|
||||||
}
|
}
|
||||||
erroredRecyclerView.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
|
erroredRecyclerView.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
|
||||||
|
|
||||||
downloadViewModel.erroredDownloads.observe(viewLifecycleOwner) {
|
lifecycleScope.launch {
|
||||||
items = it.toMutableList()
|
downloadViewModel.erroredDownloads.collectLatest {
|
||||||
erroredDownloads.submitList(it)
|
adapter.submitData(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadViewModel.getTotalSize(listOf(DownloadRepository.Status.Error)).observe(viewLifecycleOwner){
|
||||||
|
totalSize = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActionButtonClick(itemID: Long) {
|
override fun onActionButtonClick(itemID: Long) {
|
||||||
val item = items.find { it.id == itemID } ?: return
|
lifecycleScope.launch {
|
||||||
if (item.logID != null) {
|
val item = withContext(Dispatchers.IO){
|
||||||
val bundle = Bundle()
|
downloadViewModel.getItemByID(itemID)
|
||||||
bundle.putLong("logID", item.logID!!)
|
}
|
||||||
findNavController().navigate(
|
if (item.logID != null) {
|
||||||
R.id.downloadLogFragment,
|
val bundle = Bundle()
|
||||||
bundle
|
bundle.putLong("logID", item.logID!!)
|
||||||
)
|
findNavController().navigate(
|
||||||
|
R.id.downloadLogFragment,
|
||||||
|
bundle
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCardClick(itemID: Long) {
|
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())
|
UiUtil.showDownloadItemDetailsCard(
|
||||||
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
item,
|
||||||
bottomSheet.setContentView(R.layout.history_item_details_bottom_sheet)
|
requireActivity(),
|
||||||
val title = bottomSheet.findViewById<TextView>(R.id.bottom_sheet_title)
|
DownloadRepository.Status.valueOf(item.status),
|
||||||
title!!.text = item.title.ifEmpty { "`${requireContext().getString(R.string.defaultValue)}`" }
|
removeItem = { it: DownloadItem, sheet: BottomSheetDialog ->
|
||||||
val author = bottomSheet.findViewById<TextView>(R.id.bottom_sheet_author)
|
removeItem(it, sheet)
|
||||||
author!!.text = item.author.ifEmpty { "`${requireContext().getString(R.string.defaultValue)}`" }
|
|
||||||
|
|
||||||
// BUTTON ----------------------------------
|
},
|
||||||
val btn = bottomSheet.findViewById<MaterialButton>(R.id.downloads_download_button_type)
|
downloadItem = {
|
||||||
|
runBlocking{
|
||||||
when (item.type) {
|
downloadViewModel.queueDownloads(listOf(it))
|
||||||
DownloadViewModel.Type.audio -> {
|
}
|
||||||
btn!!.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_music)
|
},
|
||||||
}
|
longClickDownloadButton = {
|
||||||
DownloadViewModel.Type.video -> {
|
val sheet = DownloadBottomSheetDialog(downloadViewModel.createResultItemFromDownload(it), it.type, it, false)
|
||||||
btn!!.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_video)
|
sheet.show(parentFragmentManager, "downloadSingleSheet")
|
||||||
}
|
},
|
||||||
else -> {
|
scheduleButtonClick = {}
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCardSelect(itemID: Long, isChecked: Boolean) {
|
override fun onCardSelect(isChecked: Boolean, position: Int) {
|
||||||
val item = items.find { it.id == itemID }
|
lifecycleScope.launch {
|
||||||
if (isChecked) {
|
val selectedObjects = adapter.getSelectedObjectsCount(totalSize)
|
||||||
selectedObjects!!.add(item!!)
|
if (isChecked) {
|
||||||
if (actionMode == null){
|
if (actionMode == null){
|
||||||
actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
|
actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
|
||||||
|
|
||||||
}else{
|
}else{
|
||||||
actionMode!!.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
actionMode!!.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
else {
|
||||||
else {
|
actionMode?.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||||
selectedObjects!!.remove(item)
|
if (selectedObjects == 0){
|
||||||
actionMode?.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
actionMode?.finish()
|
||||||
if (selectedObjects!!.isEmpty()){
|
}
|
||||||
actionMode?.finish()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -238,7 +163,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
|
||||||
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + item.title + "\"!")
|
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + item.title + "\"!")
|
||||||
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||||
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||||
downloadViewModel.deleteDownload(item)
|
downloadViewModel.deleteDownload(item.id)
|
||||||
}
|
}
|
||||||
deleteDialog.show()
|
deleteDialog.show()
|
||||||
}
|
}
|
||||||
|
|
@ -252,7 +177,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
|
||||||
private val contextualActionBar = object : ActionMode.Callback {
|
private val contextualActionBar = object : ActionMode.Callback {
|
||||||
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||||
mode!!.menuInflater.inflate(R.menu.cancelled_downloads_menu_context, menu)
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -273,39 +198,53 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
|
||||||
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items))
|
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items))
|
||||||
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||||
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||||
for (obj in selectedObjects!!){
|
lifecycleScope.launch {
|
||||||
downloadViewModel.deleteDownload(obj)
|
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()
|
deleteDialog.show()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.redownload -> {
|
R.id.redownload -> {
|
||||||
runBlocking {
|
lifecycleScope.launch {
|
||||||
downloadViewModel.queueDownloads(selectedObjects!!.toMutableList())
|
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()
|
actionMode?.finish()
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.select_all -> {
|
R.id.select_all -> {
|
||||||
erroredDownloads.checkAll(items)
|
adapter.checkAll()
|
||||||
selectedObjects?.clear()
|
|
||||||
items.forEach { selectedObjects?.add(it) }
|
|
||||||
mode?.title = getString(R.string.all_items_selected)
|
mode?.title = getString(R.string.all_items_selected)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.invert_selected -> {
|
R.id.invert_selected -> {
|
||||||
erroredDownloads.invertSelected(items)
|
adapter.invertSelected()
|
||||||
val invertedList = arrayListOf<DownloadItem>()
|
val selectedObjects = adapter.getSelectedObjectsCount(totalSize)
|
||||||
items.forEach {
|
actionMode!!.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||||
if (!selectedObjects?.contains(it)!!) invertedList.add(it)
|
if (selectedObjects == 0) actionMode?.finish()
|
||||||
}
|
|
||||||
selectedObjects?.clear()
|
|
||||||
selectedObjects?.addAll(invertedList)
|
|
||||||
actionMode!!.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
|
||||||
if (invertedList.isEmpty()) actionMode?.finish()
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
else -> false
|
else -> false
|
||||||
|
|
@ -314,15 +253,10 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
|
||||||
|
|
||||||
override fun onDestroyActionMode(mode: ActionMode?) {
|
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||||
actionMode = null
|
actionMode = null
|
||||||
clearCheckedItems()
|
adapter.clearCheckedItems()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun clearCheckedItems(){
|
|
||||||
erroredDownloads.clearCheckeditems()
|
|
||||||
selectedObjects?.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
private var simpleCallback: ItemTouchHelper.SimpleCallback =
|
private var simpleCallback: ItemTouchHelper.SimpleCallback =
|
||||||
object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
|
object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
|
||||||
override fun onMove(recyclerView: RecyclerView,viewHolder: RecyclerView.ViewHolder,target: RecyclerView.ViewHolder
|
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) {
|
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||||
val position = viewHolder.bindingAdapterPosition
|
val itemID = viewHolder.itemView.tag.toString().toLong()
|
||||||
when (direction) {
|
when (direction) {
|
||||||
ItemTouchHelper.RIGHT -> {
|
ItemTouchHelper.RIGHT -> {
|
||||||
runBlocking{
|
runBlocking{
|
||||||
downloadViewModel.queueDownloads(listOf(items[position]))
|
downloadViewModel.reQueueDownloadItems(listOf(itemID))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ItemTouchHelper.LEFT -> {
|
ItemTouchHelper.LEFT -> {
|
||||||
val deletedItem = items[position]
|
lifecycleScope.launch {
|
||||||
downloadViewModel.deleteDownload(deletedItem)
|
val deletedItem = withContext(Dispatchers.IO){
|
||||||
Snackbar.make(erroredRecyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
|
downloadViewModel.getItemByID(itemID)
|
||||||
.setAction(getString(R.string.undo)) {
|
}
|
||||||
downloadViewModel.insert(deletedItem)
|
downloadViewModel.deleteDownload(deletedItem.id)
|
||||||
}.show()
|
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(
|
super.onChildDraw(
|
||||||
c,
|
c,
|
||||||
recyclerView,
|
recyclerView,
|
||||||
viewHolder!!,
|
viewHolder,
|
||||||
dX,
|
dX,
|
||||||
dY,
|
dY,
|
||||||
actionState,
|
actionState,
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ import com.deniscerri.ytdlnis.databinding.FragmentHistoryBinding
|
||||||
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
|
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
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.AppBarLayout
|
||||||
import com.google.android.material.appbar.MaterialToolbar
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
|
|
@ -127,6 +128,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
||||||
)
|
)
|
||||||
recyclerView = view.findViewById(R.id.recyclerviewhistorys)
|
recyclerView = view.findViewById(R.id.recyclerviewhistorys)
|
||||||
recyclerView?.adapter = historyAdapter
|
recyclerView?.adapter = historyAdapter
|
||||||
|
recyclerView?.enableFastScroll()
|
||||||
|
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
if (preferences.getBoolean("swipe_gestures", true)){
|
if (preferences.getBoolean("swipe_gestures", true)){
|
||||||
|
|
|
||||||
|
|
@ -6,23 +6,15 @@ import android.content.DialogInterface
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.format.DateFormat
|
|
||||||
import android.util.DisplayMetrics
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.Window
|
|
||||||
import android.widget.Button
|
|
||||||
import android.widget.EditText
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.view.ActionMode
|
import androidx.appcompat.view.ActionMode
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
|
@ -36,26 +28,23 @@ import com.deniscerri.ytdlnis.adapter.GenericDownloadAdapter
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
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.FileUtil
|
||||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
|
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil.forceFastScrollMode
|
import com.deniscerri.ytdlnis.util.UiUtil.forceFastScrollMode
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
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.color.MaterialColors
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.yausername.youtubedl_android.YoutubeDL
|
import com.yausername.youtubedl_android.YoutubeDL
|
||||||
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import java.text.SimpleDateFormat
|
import kotlinx.coroutines.withContext
|
||||||
import java.util.Calendar
|
|
||||||
import java.util.Locale
|
|
||||||
import java.util.UUID
|
|
||||||
|
|
||||||
|
|
||||||
class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickListener {
|
class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickListener {
|
||||||
|
|
@ -63,12 +52,11 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
||||||
private var activity: Activity? = null
|
private var activity: Activity? = null
|
||||||
private lateinit var downloadViewModel : DownloadViewModel
|
private lateinit var downloadViewModel : DownloadViewModel
|
||||||
private lateinit var queuedRecyclerView : RecyclerView
|
private lateinit var queuedRecyclerView : RecyclerView
|
||||||
private lateinit var queuedDownloads : GenericDownloadAdapter
|
private lateinit var adapter : GenericDownloadAdapter
|
||||||
private lateinit var notificationUtil: NotificationUtil
|
private lateinit var notificationUtil: NotificationUtil
|
||||||
private lateinit var fileSize: TextView
|
private lateinit var fileSize: TextView
|
||||||
private var selectedObjects: ArrayList<DownloadItem>? = null
|
private var totalSize: Int = 0
|
||||||
private var actionMode : ActionMode? = null
|
private var actionMode : ActionMode? = null
|
||||||
private lateinit var items : MutableList<DownloadItem>
|
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
|
|
@ -79,8 +67,6 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
||||||
activity = getActivity()
|
activity = getActivity()
|
||||||
notificationUtil = NotificationUtil(requireContext())
|
notificationUtil = NotificationUtil(requireContext())
|
||||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||||
items = mutableListOf()
|
|
||||||
selectedObjects = arrayListOf()
|
|
||||||
return fragmentView
|
return fragmentView
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,7 +74,7 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
fileSize = view.findViewById(R.id.filesize)
|
fileSize = view.findViewById(R.id.filesize)
|
||||||
queuedDownloads =
|
adapter =
|
||||||
GenericDownloadAdapter(
|
GenericDownloadAdapter(
|
||||||
this,
|
this,
|
||||||
requireActivity()
|
requireActivity()
|
||||||
|
|
@ -96,7 +82,9 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
||||||
|
|
||||||
queuedRecyclerView = view.findViewById(R.id.download_recyclerview)
|
queuedRecyclerView = view.findViewById(R.id.download_recyclerview)
|
||||||
queuedRecyclerView.forceFastScrollMode()
|
queuedRecyclerView.forceFastScrollMode()
|
||||||
queuedRecyclerView.adapter = queuedDownloads
|
queuedRecyclerView.adapter = adapter
|
||||||
|
queuedRecyclerView.enableFastScroll()
|
||||||
|
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
if (preferences.getBoolean("swipe_gestures", true)){
|
if (preferences.getBoolean("swipe_gestures", true)){
|
||||||
val itemTouchHelper = ItemTouchHelper(simpleCallback)
|
val itemTouchHelper = ItemTouchHelper(simpleCallback)
|
||||||
|
|
@ -104,18 +92,34 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
||||||
}
|
}
|
||||||
queuedRecyclerView.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
|
queuedRecyclerView.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
|
||||||
|
|
||||||
downloadViewModel.queuedDownloads.observe(viewLifecycleOwner) {
|
lifecycleScope.launch {
|
||||||
items = it.toMutableList()
|
downloadViewModel.queuedDownloads.collectLatest {
|
||||||
if (it.isEmpty()) fileSize.visibility = View.GONE
|
adapter.submitData(it)
|
||||||
else{
|
}
|
||||||
val size = FileUtil.convertFileSize(it.sumOf { i -> i.format.filesize })
|
}
|
||||||
if (size == "?") fileSize.visibility = View.GONE
|
|
||||||
else {
|
adapter.addLoadStateListener { loadState ->
|
||||||
fileSize.visibility = View.VISIBLE
|
lifecycleScope.launch {
|
||||||
fileSize.text = "${getString(R.string.file_size)}: ~ $size"
|
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) {
|
override fun onCardClick(itemID: Long) {
|
||||||
val item = items.find { it.id == itemID } ?: return
|
lifecycleScope.launch {
|
||||||
|
val item = withContext(Dispatchers.IO){
|
||||||
val bottomSheet = BottomSheetDialog(requireContext())
|
downloadViewModel.getItemByID(itemID)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val time = bottomSheet.findViewById<Chip>(R.id.time)
|
UiUtil.showDownloadItemDetailsCard(
|
||||||
val formatNote = bottomSheet.findViewById<Chip>(R.id.format_note)
|
item,
|
||||||
val container = bottomSheet.findViewById<Chip>(R.id.container_chip)
|
requireActivity(),
|
||||||
val codec = bottomSheet.findViewById<Chip>(R.id.codec)
|
DownloadRepository.Status.valueOf(item.status),
|
||||||
val fileSize = bottomSheet.findViewById<Chip>(R.id.file_size)
|
removeItem = { it: DownloadItem, sheet: BottomSheetDialog ->
|
||||||
|
sheet.hide()
|
||||||
if (item.downloadStartTime <= System.currentTimeMillis() / 1000) time!!.visibility = View.GONE
|
removeItem(it.id)
|
||||||
else {
|
},
|
||||||
val calendar = Calendar.getInstance()
|
downloadItem = {
|
||||||
calendar.timeInMillis = item.downloadStartTime
|
downloadViewModel.deleteDownload(it.id)
|
||||||
time!!.text = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(calendar.time)
|
it.downloadStartTime = 0
|
||||||
|
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(it.id.toString())
|
||||||
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())
|
|
||||||
runBlocking {
|
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) {
|
override fun onCardSelect(isChecked: Boolean, position: Int) {
|
||||||
val item = items.find { it.id == itemID }
|
lifecycleScope.launch {
|
||||||
val now = System.currentTimeMillis()
|
val selectedObjects = adapter.getSelectedObjectsCount(totalSize)
|
||||||
if (isChecked) {
|
val now = System.currentTimeMillis()
|
||||||
selectedObjects!!.add(item!!)
|
if (isChecked) {
|
||||||
if (actionMode == null){
|
if (actionMode == null){
|
||||||
actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
|
actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
|
||||||
|
}else{
|
||||||
|
|
||||||
}else{
|
actionMode!!.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||||
actionMode!!.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
}
|
||||||
}
|
}
|
||||||
}
|
else {
|
||||||
else {
|
actionMode?.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||||
selectedObjects!!.remove(item)
|
if (selectedObjects == 0){
|
||||||
actionMode?.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
actionMode?.finish()
|
||||||
if (selectedObjects!!.isEmpty()){
|
}
|
||||||
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){
|
private fun removeItem(id: Long){
|
||||||
val item = items.find { it.id == id } ?: return
|
lifecycleScope.launch {
|
||||||
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
|
val item = withContext(Dispatchers.IO){
|
||||||
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + item.title + "\"!")
|
downloadViewModel.getItemByID(id)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
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)
|
Snackbar.make(queuedRecyclerView, getString(R.string.cancelled) + ": " + item.title, Snackbar.LENGTH_LONG)
|
||||||
.setAction(getString(R.string.undo)) {
|
.setAction(getString(R.string.undo)) {
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
downloadViewModel.deleteDownload(item)
|
downloadViewModel.deleteDownload(item.id)
|
||||||
downloadViewModel.queueDownloads(listOf(item))
|
downloadViewModel.queueDownloads(listOf(item))
|
||||||
}
|
}
|
||||||
}.show()
|
}.show()
|
||||||
|
}
|
||||||
|
deleteDialog.show()
|
||||||
}
|
}
|
||||||
deleteDialog.show()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val contextualActionBar = object : ActionMode.Callback {
|
private val contextualActionBar = object : ActionMode.Callback {
|
||||||
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||||
mode!!.menuInflater.inflate(R.menu.queued_menu_context, menu)
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -318,46 +246,81 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
||||||
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items))
|
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items))
|
||||||
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||||
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||||
for (obj in selectedObjects!!){
|
lifecycleScope.launch {
|
||||||
val id = obj.id.toInt()
|
val selectedObjects = if (adapter.inverted || adapter.checkedItems.isEmpty()){
|
||||||
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
withContext(Dispatchers.IO){
|
||||||
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(id.toString())
|
downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems, listOf(
|
||||||
notificationUtil.cancelDownloadNotification(id)
|
DownloadRepository.Status.Queued, DownloadRepository.Status.QueuedPaused))
|
||||||
downloadViewModel.deleteDownload(obj)
|
}
|
||||||
|
}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()
|
deleteDialog.show()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.download -> {
|
R.id.download -> {
|
||||||
for (obj in selectedObjects!!){
|
lifecycleScope.launch {
|
||||||
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(obj.id.toInt().toString())
|
val selectedObjects = if (adapter.inverted || adapter.checkedItems.isEmpty()){
|
||||||
}
|
withContext(Dispatchers.IO){
|
||||||
selectedObjects!!.forEach { it.downloadStartTime = 0L }
|
downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems, listOf(
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
DownloadRepository.Status.Queued, DownloadRepository.Status.QueuedPaused))
|
||||||
downloadViewModel.queueDownloads(selectedObjects!!)
|
}
|
||||||
|
}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
|
true
|
||||||
}
|
}
|
||||||
R.id.select_all -> {
|
R.id.select_all -> {
|
||||||
queuedDownloads.checkAll(items)
|
adapter.checkAll()
|
||||||
selectedObjects?.clear()
|
|
||||||
items.forEach { selectedObjects?.add(it) }
|
|
||||||
mode?.title = getString(R.string.all_items_selected)
|
mode?.title = getString(R.string.all_items_selected)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.invert_selected -> {
|
R.id.invert_selected -> {
|
||||||
queuedDownloads.invertSelected(items)
|
adapter.invertSelected()
|
||||||
val invertedList = arrayListOf<DownloadItem>()
|
val selectedObjects = adapter.getSelectedObjectsCount(totalSize)
|
||||||
items.forEach {
|
actionMode!!.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||||
if (!selectedObjects?.contains(it)!!) invertedList.add(it)
|
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
|
true
|
||||||
}
|
}
|
||||||
else -> false
|
else -> false
|
||||||
|
|
@ -366,14 +329,10 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
||||||
|
|
||||||
override fun onDestroyActionMode(mode: ActionMode?) {
|
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||||
actionMode = null
|
actionMode = null
|
||||||
clearCheckedItems()
|
adapter.clearCheckedItems()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun clearCheckedItems(){
|
|
||||||
queuedDownloads.clearCheckeditems()
|
|
||||||
selectedObjects?.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
private var simpleCallback: ItemTouchHelper.SimpleCallback =
|
private var simpleCallback: ItemTouchHelper.SimpleCallback =
|
||||||
object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
|
object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
|
||||||
|
|
@ -383,12 +342,16 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||||
val position = viewHolder.bindingAdapterPosition
|
val itemID = viewHolder.itemView.tag.toString().toLong()
|
||||||
when (direction) {
|
when (direction) {
|
||||||
ItemTouchHelper.LEFT -> {
|
ItemTouchHelper.LEFT -> {
|
||||||
val deletedItem = items[position]
|
lifecycleScope.launch {
|
||||||
queuedDownloads.notifyItemChanged(position)
|
val deletedItem = withContext(Dispatchers.IO){
|
||||||
removeItem(deletedItem.id)
|
downloadViewModel.getItemByID(itemID)
|
||||||
|
}
|
||||||
|
adapter.notifyItemChanged(viewHolder.bindingAdapterPosition)
|
||||||
|
removeItem(deletedItem.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -426,7 +389,7 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
||||||
super.onChildDraw(
|
super.onChildDraw(
|
||||||
c,
|
c,
|
||||||
recyclerView,
|
recyclerView,
|
||||||
viewHolder!!,
|
viewHolder,
|
||||||
dX,
|
dX,
|
||||||
dY,
|
dY,
|
||||||
actionState,
|
actionState,
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,21 @@
|
||||||
package com.deniscerri.ytdlnis.ui.downloads
|
package com.deniscerri.ytdlnis.ui.downloads
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.DisplayMetrics
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.Window
|
|
||||||
import android.widget.Button
|
|
||||||
import android.widget.TextView
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.view.ActionMode
|
import androidx.appcompat.view.ActionMode
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
|
|
@ -29,19 +23,22 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.adapter.GenericDownloadAdapter
|
import com.deniscerri.ytdlnis.adapter.GenericDownloadAdapter
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
|
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
|
||||||
|
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil.forceFastScrollMode
|
import com.deniscerri.ytdlnis.util.UiUtil.forceFastScrollMode
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
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.color.MaterialColors
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
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.runBlocking
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
|
||||||
class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickListener {
|
class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickListener {
|
||||||
|
|
@ -49,11 +46,9 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
|
||||||
private var activity: Activity? = null
|
private var activity: Activity? = null
|
||||||
private lateinit var downloadViewModel : DownloadViewModel
|
private lateinit var downloadViewModel : DownloadViewModel
|
||||||
private lateinit var savedRecyclerView: RecyclerView
|
private lateinit var savedRecyclerView: RecyclerView
|
||||||
private lateinit var savedDownloads: GenericDownloadAdapter
|
private lateinit var adapter: GenericDownloadAdapter
|
||||||
private var selectedObjects: ArrayList<DownloadItem>? = null
|
|
||||||
private var actionMode : ActionMode? = null
|
private var actionMode : ActionMode? = null
|
||||||
private lateinit var items : MutableList<DownloadItem>
|
private var totalSize : Int = 0
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
|
|
@ -61,16 +56,14 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
|
||||||
): View? {
|
): View? {
|
||||||
fragmentView = inflater.inflate(R.layout.fragment_generic_download_queue, container, false)
|
fragmentView = inflater.inflate(R.layout.fragment_generic_download_queue, container, false)
|
||||||
activity = getActivity()
|
activity = getActivity()
|
||||||
selectedObjects = arrayListOf()
|
|
||||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||||
items = mutableListOf()
|
|
||||||
return fragmentView
|
return fragmentView
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
savedDownloads =
|
adapter =
|
||||||
GenericDownloadAdapter(
|
GenericDownloadAdapter(
|
||||||
this,
|
this,
|
||||||
requireActivity()
|
requireActivity()
|
||||||
|
|
@ -78,7 +71,8 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
|
||||||
|
|
||||||
savedRecyclerView = view.findViewById(R.id.download_recyclerview)
|
savedRecyclerView = view.findViewById(R.id.download_recyclerview)
|
||||||
savedRecyclerView.forceFastScrollMode()
|
savedRecyclerView.forceFastScrollMode()
|
||||||
savedRecyclerView.adapter = savedDownloads
|
savedRecyclerView.enableFastScroll()
|
||||||
|
savedRecyclerView.adapter = adapter
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
if (preferences.getBoolean("swipe_gestures", true)){
|
if (preferences.getBoolean("swipe_gestures", true)){
|
||||||
val itemTouchHelper = ItemTouchHelper(simpleCallback)
|
val itemTouchHelper = ItemTouchHelper(simpleCallback)
|
||||||
|
|
@ -87,148 +81,76 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
|
||||||
savedRecyclerView.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
|
savedRecyclerView.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
|
||||||
|
|
||||||
|
|
||||||
downloadViewModel.savedDownloads.observe(viewLifecycleOwner) {
|
lifecycleScope.launch {
|
||||||
items = it.toMutableList()
|
downloadViewModel.savedDownloads.collectLatest {
|
||||||
savedDownloads.submitList(it)
|
adapter.submitData(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadViewModel.getTotalSize(listOf(DownloadRepository.Status.Saved)).observe(viewLifecycleOwner){
|
||||||
|
totalSize = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActionButtonClick(itemID: Long) {
|
override fun onActionButtonClick(itemID: Long) {
|
||||||
val item = items.find { it.id == itemID }
|
lifecycleScope.launch {
|
||||||
if (item != null){
|
val item = withContext(Dispatchers.IO){
|
||||||
runBlocking {
|
downloadViewModel.getItemByID(itemID)
|
||||||
downloadViewModel.queueDownloads(listOf(item))
|
}
|
||||||
|
|
||||||
|
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) {
|
override fun onCardClick(itemID: Long) {
|
||||||
val item = items.find { it.id == itemID } ?: return
|
lifecycleScope.launch {
|
||||||
|
val item = withContext(Dispatchers.IO){
|
||||||
val bottomSheet = BottomSheetDialog(requireContext())
|
downloadViewModel.getItemByID(itemID)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
override fun onCardSelect(isChecked: Boolean, position: Int) {
|
||||||
val item = items.find { it.id == itemID }
|
lifecycleScope.launch {
|
||||||
if (isChecked) {
|
val selectedObjects = adapter.getSelectedObjectsCount(totalSize)
|
||||||
selectedObjects!!.add(item!!)
|
if (isChecked) {
|
||||||
if (actionMode == null){
|
if (actionMode == null){
|
||||||
actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
|
actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
|
||||||
|
|
||||||
}else{
|
}else{
|
||||||
actionMode!!.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
actionMode!!.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
else {
|
||||||
else {
|
actionMode?.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||||
selectedObjects!!.remove(item)
|
if (selectedObjects == 0){
|
||||||
actionMode?.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
actionMode?.finish()
|
||||||
if (selectedObjects!!.isEmpty()){
|
}
|
||||||
actionMode?.finish()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -240,7 +162,7 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
|
||||||
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + item.title + "\"!")
|
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + item.title + "\"!")
|
||||||
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||||
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||||
downloadViewModel.deleteDownload(item)
|
downloadViewModel.deleteDownload(item.id)
|
||||||
}
|
}
|
||||||
deleteDialog.show()
|
deleteDialog.show()
|
||||||
}
|
}
|
||||||
|
|
@ -249,7 +171,7 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
|
||||||
private val contextualActionBar = object : ActionMode.Callback {
|
private val contextualActionBar = object : ActionMode.Callback {
|
||||||
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||||
mode!!.menuInflater.inflate(R.menu.saved_downloads_menu_context, menu)
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -270,39 +192,54 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
|
||||||
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items))
|
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items))
|
||||||
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||||
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||||
for (obj in selectedObjects!!){
|
lifecycleScope.launch {
|
||||||
downloadViewModel.deleteDownload(obj)
|
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()
|
deleteDialog.show()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.download -> {
|
R.id.download -> {
|
||||||
runBlocking {
|
lifecycleScope.launch {
|
||||||
downloadViewModel.queueDownloads(selectedObjects!!.toMutableList())
|
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()
|
actionMode?.finish()
|
||||||
|
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.select_all -> {
|
R.id.select_all -> {
|
||||||
savedDownloads.checkAll(items)
|
adapter.checkAll()
|
||||||
selectedObjects?.clear()
|
|
||||||
items.forEach { selectedObjects?.add(it) }
|
|
||||||
mode?.title = getString(R.string.all_items_selected)
|
mode?.title = getString(R.string.all_items_selected)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.invert_selected -> {
|
R.id.invert_selected -> {
|
||||||
savedDownloads.invertSelected(items)
|
adapter.invertSelected()
|
||||||
val invertedList = arrayListOf<DownloadItem>()
|
val selectedObjects = adapter.getSelectedObjectsCount(totalSize)
|
||||||
items.forEach {
|
actionMode!!.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||||
if (!selectedObjects?.contains(it)!!) invertedList.add(it)
|
if (selectedObjects == 0) actionMode?.finish()
|
||||||
}
|
|
||||||
selectedObjects?.clear()
|
|
||||||
selectedObjects?.addAll(invertedList)
|
|
||||||
actionMode!!.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
|
||||||
if (invertedList.isEmpty()) actionMode?.finish()
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
else -> false
|
else -> false
|
||||||
|
|
@ -311,14 +248,10 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
|
||||||
|
|
||||||
override fun onDestroyActionMode(mode: ActionMode?) {
|
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||||
actionMode = null
|
actionMode = null
|
||||||
clearCheckedItems()
|
adapter.clearCheckedItems()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun clearCheckedItems(){
|
|
||||||
savedDownloads.clearCheckeditems()
|
|
||||||
selectedObjects?.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
private var simpleCallback: ItemTouchHelper.SimpleCallback =
|
private var simpleCallback: ItemTouchHelper.SimpleCallback =
|
||||||
object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
|
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) {
|
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||||
val position = viewHolder.bindingAdapterPosition
|
val itemID = viewHolder.itemView.tag.toString().toLong()
|
||||||
when (direction) {
|
when (direction) {
|
||||||
ItemTouchHelper.LEFT -> {
|
ItemTouchHelper.LEFT -> {
|
||||||
val deletedItem = items[position]
|
lifecycleScope.launch {
|
||||||
downloadViewModel.deleteDownload(deletedItem)
|
val deletedItem = withContext(Dispatchers.IO){
|
||||||
Snackbar.make(savedRecyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
|
downloadViewModel.getItemByID(itemID)
|
||||||
.setAction(getString(R.string.undo)) {
|
}
|
||||||
downloadViewModel.insert(deletedItem)
|
downloadViewModel.deleteDownload(deletedItem.id)
|
||||||
}.show()
|
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 -> {
|
ItemTouchHelper.RIGHT -> {
|
||||||
onActionButtonClick(items[position].id)
|
onActionButtonClick(itemID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -377,7 +314,7 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
|
||||||
super.onChildDraw(
|
super.onChildDraw(
|
||||||
c,
|
c,
|
||||||
recyclerView,
|
recyclerView,
|
||||||
viewHolder!!,
|
viewHolder,
|
||||||
dX,
|
dX,
|
||||||
dY,
|
dY,
|
||||||
actionState,
|
actionState,
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.adapter.CookieAdapter
|
import com.deniscerri.ytdlnis.adapter.CookieAdapter
|
||||||
import com.deniscerri.ytdlnis.database.models.CookieItem
|
import com.deniscerri.ytdlnis.database.models.CookieItem
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel
|
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.appbar.MaterialToolbar
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.deniscerri.ytdlnis.ui.more
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
import android.webkit.CookieManager
|
import android.webkit.CookieManager
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
import android.webkit.WebViewClient
|
import android.webkit.WebViewClient
|
||||||
|
|
@ -68,6 +69,7 @@ class WebViewActivity : BaseActivity() {
|
||||||
|
|
||||||
generateBtn.setOnClickListener {
|
generateBtn.setOnClickListener {
|
||||||
cookiesViewModel.getCookiesFromDB().getOrNull()?.let {
|
cookiesViewModel.getCookiesFromDB().getOrNull()?.let {
|
||||||
|
Log.e("asas", it)
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
withContext(Dispatchers.IO){
|
withContext(Dispatchers.IO){
|
||||||
|
|
|
||||||
|
|
@ -31,28 +31,6 @@ class DownloadSettingsFragment : BaseSettingsFragment() {
|
||||||
ignoreBatteryOptimization!!.isVisible = false
|
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 = findPreference("ignore_battery")
|
||||||
ignoreBatteryOptimization!!.onPreferenceClickListener =
|
ignoreBatteryOptimization!!.onPreferenceClickListener =
|
||||||
Preference.OnPreferenceClickListener {
|
Preference.OnPreferenceClickListener {
|
||||||
|
|
|
||||||
|
|
@ -113,14 +113,14 @@ class FolderSettingsFragment : BaseSettingsFragment() {
|
||||||
audioFilenameTemplate?.title = "${getString(R.string.file_name_template)} [${getString(R.string.audio)}]"
|
audioFilenameTemplate?.title = "${getString(R.string.file_name_template)} [${getString(R.string.audio)}]"
|
||||||
audioFilenameTemplate?.dialogTitle = "${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!!.summary = "(${FileUtil.convertFileSize(cacheSize)}) ${resources.getString(R.string.clear_temporary_files_summary)}"
|
||||||
clearCache!!.onPreferenceClickListener =
|
clearCache!!.onPreferenceClickListener =
|
||||||
Preference.OnPreferenceClickListener {
|
Preference.OnPreferenceClickListener {
|
||||||
if (activeDownloadCount == 0){
|
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()
|
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)}"
|
clearCache!!.summary = "(${FileUtil.convertFileSize(cacheSize)}) ${resources.getString(R.string.clear_temporary_files_summary)}"
|
||||||
}else{
|
}else{
|
||||||
Snackbar.make(requireView(), getString(R.string.downloads_running_try_later), Snackbar.LENGTH_SHORT).show()
|
Snackbar.make(requireView(), getString(R.string.downloads_running_try_later), Snackbar.LENGTH_SHORT).show()
|
||||||
|
|
|
||||||
|
|
@ -201,7 +201,7 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
||||||
version = findPreference("version")
|
version = findPreference("version")
|
||||||
val nativeLibraryDir = context?.applicationInfo?.nativeLibraryDir
|
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 =
|
version!!.onPreferenceClickListener =
|
||||||
Preference.OnPreferenceClickListener {
|
Preference.OnPreferenceClickListener {
|
||||||
|
|
@ -314,6 +314,7 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
||||||
}
|
}
|
||||||
val arr = JsonArray()
|
val arr = JsonArray()
|
||||||
items.forEach {
|
items.forEach {
|
||||||
|
it.useAsExtraCommand = false
|
||||||
arr.add(JsonParser().parse(Gson().toJson(it)).asJsonObject)
|
arr.add(JsonParser().parse(Gson().toJson(it)).asJsonObject)
|
||||||
}
|
}
|
||||||
return arr
|
return arr
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import android.os.storage.StorageVolume
|
||||||
import android.provider.DocumentsContract
|
import android.provider.DocumentsContract
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.webkit.MimeTypeMap
|
import android.webkit.MimeTypeMap
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
import androidx.core.net.toFile
|
import androidx.core.net.toFile
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.documentfile.provider.DocumentFile
|
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.getAbsolutePath
|
||||||
import com.anggrayudi.storage.file.getBasePath
|
import com.anggrayudi.storage.file.getBasePath
|
||||||
import com.deniscerri.ytdlnis.App
|
import com.deniscerri.ytdlnis.App
|
||||||
|
import com.deniscerri.ytdlnis.BuildConfig
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
@ -27,11 +29,13 @@ import okhttp3.internal.closeQuietly
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.URLDecoder
|
import java.net.URLDecoder
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.nio.file.CopyOption
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.StandardCopyOption
|
import java.nio.file.StandardCopyOption
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
import java.text.DecimalFormatSymbols
|
import java.text.DecimalFormatSymbols
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlin.io.path.absolutePathString
|
||||||
import kotlin.math.log10
|
import kotlin.math.log10
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
|
|
||||||
|
|
@ -87,7 +91,7 @@ object FileUtil {
|
||||||
if (it.isDirectory && it.absolutePath == originDir.absolutePath) return@forEach
|
if (it.isDirectory && it.absolutePath == originDir.absolutePath) return@forEach
|
||||||
var destFile: DocumentFile
|
var destFile: DocumentFile
|
||||||
try {
|
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()
|
it.delete()
|
||||||
return@forEach
|
return@forEach
|
||||||
}
|
}
|
||||||
|
|
@ -182,8 +186,15 @@ object FileUtil {
|
||||||
super.onFailed(errorCode)
|
super.onFailed(errorCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onConflict(
|
||||||
|
destinationFile: DocumentFile,
|
||||||
|
action: FileConflictAction
|
||||||
|
) {
|
||||||
|
action.confirmResolution(ConflictResolution.CREATE_NEW)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onStart(file: Any, workerThread: Thread): Long {
|
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) {
|
override fun onReport(report: Report) {
|
||||||
|
|
@ -204,17 +215,37 @@ object FileUtil {
|
||||||
val files = it.listFiles()?.filter { fil -> !fil.isDirectory }?.toTypedArray() ?: arrayOf(it)
|
val files = it.listFiles()?.filter { fil -> !fil.isDirectory }?.toTypedArray() ?: arrayOf(it)
|
||||||
for (ff in files){
|
for (ff in files){
|
||||||
val newFile = File(dir.absolutePath + "/${ff.absolutePath.removePrefix(originDir.absolutePath)}")
|
val newFile = File(dir.absolutePath + "/${ff.absolutePath.removePrefix(originDir.absolutePath)}")
|
||||||
newFile.mkdirs()
|
runCatching {
|
||||||
if (Build.VERSION.SDK_INT >= 26 ) {
|
newFile.parentFile?.mkdirs()
|
||||||
Files.move(
|
}
|
||||||
ff.toPath(),
|
if (Build.VERSION.SDK_INT >= 26 ) {
|
||||||
newFile.toPath(),
|
var newFileName = newFile.absolutePath
|
||||||
StandardCopyOption.REPLACE_EXISTING
|
var counter = 1
|
||||||
)
|
while (Files.exists(File(newFileName).toPath())) {
|
||||||
}else{
|
// If the file already exists in the destination directory, add a number to differentiate it
|
||||||
ff.renameTo(newFile)
|
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
|
return@forEach
|
||||||
}
|
}
|
||||||
|
|
@ -238,17 +269,25 @@ object FileUtil {
|
||||||
return listOf(context.getString(R.string.unfound_file))
|
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{
|
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{
|
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 {
|
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{
|
fun convertFileSize(s: Long): String{
|
||||||
|
|
|
||||||
|
|
@ -448,14 +448,14 @@ class InfoUtil(private val context: Context) {
|
||||||
try {
|
try {
|
||||||
val request = YoutubeDLRequest(url)
|
val request = YoutubeDLRequest(url)
|
||||||
request.addOption("--print", "%(formats)s")
|
request.addOption("--print", "%(formats)s")
|
||||||
|
request.addOption("--print", "%(duration)s")
|
||||||
request.addOption("--skip-download")
|
request.addOption("--skip-download")
|
||||||
request.addOption("-R", "1")
|
request.addOption("-R", "1")
|
||||||
request.addOption("--socket-timeout", "5")
|
request.addOption("--socket-timeout", "5")
|
||||||
|
|
||||||
if (sharedPreferences.getBoolean("use_cookies", false)){
|
if (sharedPreferences.getBoolean("use_cookies", false)){
|
||||||
val cookiesFile = File(context.cacheDir, "cookies.txt")
|
FileUtil.getCookieFile(context){
|
||||||
if (cookiesFile.exists()) {
|
request.addOption("--cookies", it)
|
||||||
request.addOption("--cookies", cookiesFile.absolutePath)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -476,10 +476,10 @@ class InfoUtil(private val context: Context) {
|
||||||
|
|
||||||
return parseYTDLFormats(jsonArray)
|
return parseYTDLFormats(jsonArray)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
Looper.prepare().run {
|
Looper.prepare().run {
|
||||||
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
|
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
e.printStackTrace()
|
|
||||||
}
|
}
|
||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
|
|
@ -507,9 +507,8 @@ class InfoUtil(private val context: Context) {
|
||||||
|
|
||||||
|
|
||||||
if (sharedPreferences.getBoolean("use_cookies", false)){
|
if (sharedPreferences.getBoolean("use_cookies", false)){
|
||||||
val cookiesFile = File(context.cacheDir, "cookies.txt")
|
FileUtil.getCookieFile(context){
|
||||||
if (cookiesFile.exists()){
|
request.addOption("--cookies", it)
|
||||||
request.addOption("--cookies", cookiesFile.absolutePath)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -569,9 +568,8 @@ class InfoUtil(private val context: Context) {
|
||||||
request.addOption("--socket-timeout", "5")
|
request.addOption("--socket-timeout", "5")
|
||||||
|
|
||||||
if (sharedPreferences.getBoolean("use_cookies", false)){
|
if (sharedPreferences.getBoolean("use_cookies", false)){
|
||||||
val cookiesFile = File(context.cacheDir, "cookies.txt")
|
FileUtil.getCookieFile(context){
|
||||||
if (cookiesFile.exists()){
|
request.addOption("--cookies", it)
|
||||||
request.addOption("--cookies", cookiesFile.absolutePath)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -611,7 +609,13 @@ class InfoUtil(private val context: Context) {
|
||||||
duration = formatIntegerDuration(jsonObject.getInt("duration"), Locale.US)
|
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? = ""
|
var thumb: String? = ""
|
||||||
if (jsonObject.has("thumbnail")) {
|
if (jsonObject.has("thumbnail")) {
|
||||||
thumb = jsonObject.getString("thumbnail")
|
thumb = jsonObject.getString("thumbnail")
|
||||||
|
|
@ -722,9 +726,8 @@ class InfoUtil(private val context: Context) {
|
||||||
request.addOption("--socket-timeout", "5")
|
request.addOption("--socket-timeout", "5")
|
||||||
|
|
||||||
if (sharedPreferences.getBoolean("use_cookies", false)){
|
if (sharedPreferences.getBoolean("use_cookies", false)){
|
||||||
val cookiesFile = File(context.cacheDir, "cookies.txt")
|
FileUtil.getCookieFile(context){
|
||||||
if (cookiesFile.exists()){
|
request.addOption("--cookies", it)
|
||||||
request.addOption("--cookies", cookiesFile.absolutePath)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -885,9 +888,8 @@ class InfoUtil(private val context: Context) {
|
||||||
request.addOption("--socket-timeout", "5")
|
request.addOption("--socket-timeout", "5")
|
||||||
|
|
||||||
if (sharedPreferences.getBoolean("use_cookies", false)){
|
if (sharedPreferences.getBoolean("use_cookies", false)){
|
||||||
val cookiesFile = File(context.cacheDir, "cookies.txt")
|
FileUtil.getCookieFile(context){
|
||||||
if (cookiesFile.exists()){
|
request.addOption("--cookies", it)
|
||||||
request.addOption("--cookies", cookiesFile.absolutePath)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -927,7 +929,7 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun buildYoutubeDLRequest(downloadItem: DownloadItem) : YoutubeDLRequest{
|
fun buildYoutubeDLRequest(downloadItem: DownloadItem) : YoutubeDLRequest{
|
||||||
val cacheDir = File(context.cacheDir.absolutePath + "/downloads/")
|
val cacheDir = FileUtil.getCachePath()
|
||||||
val tempFileDir = File(cacheDir, downloadItem.id.toString())
|
val tempFileDir = File(cacheDir, downloadItem.id.toString())
|
||||||
tempFileDir.delete()
|
tempFileDir.delete()
|
||||||
tempFileDir.mkdirs()
|
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 (sharedPreferences.getBoolean("use_sponsorblock", true)){
|
||||||
if (filters.isNotBlank()) {
|
if (sponsorBlockFilters.isNotEmpty()) {
|
||||||
request.addOption("--sponsorblock-remove", filters)
|
val filters = java.lang.String.join(",", sponsorBlockFilters.filter { it.isNotBlank() })
|
||||||
if (sharedPreferences.getBoolean("force_keyframes", false)){
|
if (filters.isNotBlank()) {
|
||||||
request.addOption("--force-keyframes-at-cuts")
|
request.addOption("--sponsorblock-remove", filters)
|
||||||
|
if (sharedPreferences.getBoolean("force_keyframes", false)){
|
||||||
|
request.addOption("--force-keyframes-at-cuts")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if(downloadItem.title.isNotBlank()){
|
if(downloadItem.title.isNotBlank()){
|
||||||
request.addCommands(listOf("--replace-in-metadata","title",".*.",downloadItem.title.take(200)))
|
request.addCommands(listOf("--replace-in-metadata","title",".*.",downloadItem.title.take(200)))
|
||||||
}
|
}
|
||||||
if (downloadItem.author.isNotBlank()){
|
if (downloadItem.author.isNotBlank()){
|
||||||
request.addCommands(listOf("--replace-in-metadata","uploader",".*.",downloadItem.author.take(25)))
|
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$",""))
|
request.addCommands(listOf("--replace-in-metadata","uploader"," - Topic$",""))
|
||||||
|
|
||||||
downloadItem.customFileNameTemplate = downloadItem.customFileNameTemplate.replace("%(uploader)s", "%(uploader|${downloadItem.author})s")
|
|
||||||
|
|
||||||
if (downloadItem.downloadSections.isNotBlank()){
|
if (downloadItem.downloadSections.isNotBlank()){
|
||||||
downloadItem.downloadSections.split(";").forEach {
|
downloadItem.downloadSections.split(";").forEach {
|
||||||
if (it.isBlank()) return@forEach
|
if (it.isBlank()) return@forEach
|
||||||
|
|
@ -1004,7 +1009,7 @@ class InfoUtil(private val context: Context) {
|
||||||
request.addOption("--force-keyframes-at-cuts")
|
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", " ")
|
request.addOption("--output-na-placeholder", " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1013,7 +1018,7 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (downloadItem.extraCommands.isNotBlank()){
|
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.createNewFile()
|
||||||
conf.writeText(downloadItem.extraCommands)
|
conf.writeText(downloadItem.extraCommands)
|
||||||
request.addOption(
|
request.addOption(
|
||||||
|
|
@ -1032,9 +1037,8 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sharedPreferences.getBoolean("use_cookies", false)){
|
if (sharedPreferences.getBoolean("use_cookies", false)){
|
||||||
val cookiesFile = File(context.cacheDir, "cookies.txt")
|
FileUtil.getCookieFile(context){
|
||||||
if (cookiesFile.exists()){
|
request.addOption("--cookies", it)
|
||||||
request.addOption("--cookies", cookiesFile.absolutePath)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1093,7 +1097,7 @@ class InfoUtil(private val context: Context) {
|
||||||
if (! request.hasOption("--convert-thumbnails")) request.addOption("--convert-thumbnails", "jpg")
|
if (! request.hasOption("--convert-thumbnails")) request.addOption("--convert-thumbnails", "jpg")
|
||||||
if (sharedPreferences.getBoolean("crop_thumbnail", true)){
|
if (sharedPreferences.getBoolean("crop_thumbnail", true)){
|
||||||
try {
|
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)'\\\"\""
|
val configData = "--ppa \"ffmpeg: -c:v mjpeg -vf crop=\\\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\\\"\""
|
||||||
config.writeText(configData)
|
config.writeText(configData)
|
||||||
request.addOption("--ppa", "ThumbnailsConvertor:-qmin 1 -q:v 1")
|
request.addOption("--ppa", "ThumbnailsConvertor:-qmin 1 -q:v 1")
|
||||||
|
|
@ -1118,7 +1122,9 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (downloadItem.videoPreferences.addChapters) {
|
if (downloadItem.videoPreferences.addChapters) {
|
||||||
request.addOption("--sponsorblock-mark", "all")
|
if (sharedPreferences.getBoolean("use_sponsorblock", true)){
|
||||||
|
request.addOption("--sponsorblock-mark", "all")
|
||||||
|
}
|
||||||
request.addOption("--embed-chapters")
|
request.addOption("--embed-chapters")
|
||||||
}
|
}
|
||||||
if (downloadItem.videoPreferences.embedSubs) {
|
if (downloadItem.videoPreferences.embedSubs) {
|
||||||
|
|
@ -1146,10 +1152,18 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
Log.e(DownloadWorker.TAG, formatArgument)
|
Log.e(DownloadWorker.TAG, formatArgument)
|
||||||
request.addOption("-f", formatArgument)
|
request.addOption("-f", formatArgument)
|
||||||
val outputFormat = downloadItem.container
|
|
||||||
if(outputFormat.isNotEmpty() && outputFormat != "Default" && outputFormat != context.getString(R.string.defaultValue) && supportedContainers.contains(outputFormat)){
|
if (downloadItem.format.container.equals("mpeg_4", true)) downloadItem.format.container = "mp4"
|
||||||
request.addOption("--merge-output-format", outputFormat.lowercase())
|
val outputContainer = downloadItem.container
|
||||||
if (outputFormat != "webm") {
|
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)
|
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
|
||||||
if (embedThumb) {
|
if (embedThumb) {
|
||||||
request.addOption("--embed-thumbnail")
|
request.addOption("--embed-thumbnail")
|
||||||
|
|
@ -1188,7 +1202,7 @@ class InfoUtil(private val context: Context) {
|
||||||
DownloadViewModel.Type.command -> {
|
DownloadViewModel.Type.command -> {
|
||||||
request.addOption(
|
request.addOption(
|
||||||
"--config-locations",
|
"--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)
|
writeText(downloadItem.format.format_note)
|
||||||
}.absolutePath
|
}.absolutePath
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,25 @@ class NotificationUtil(var context: Context) {
|
||||||
return downloadNotificationBuilder
|
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(
|
fun createDownloadServiceNotification(
|
||||||
pendingIntent: PendingIntent?,
|
pendingIntent: PendingIntent?,
|
||||||
|
|
@ -188,6 +207,7 @@ class NotificationUtil(var context: Context) {
|
||||||
R.drawable.ic_launcher_foreground_large
|
R.drawable.ic_launcher_foreground_large
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
.setContentText("")
|
||||||
.setPriority(NotificationCompat.PRIORITY_MAX)
|
.setPriority(NotificationCompat.PRIORITY_MAX)
|
||||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||||
.clearActions()
|
.clearActions()
|
||||||
|
|
@ -277,6 +297,11 @@ class NotificationUtil(var context: Context) {
|
||||||
notificationManager.notify(DOWNLOAD_FINISHED_NOTIFICATION_ID, notificationBuilder.build())
|
notificationManager.notify(DOWNLOAD_FINISHED_NOTIFICATION_ID, notificationBuilder.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun notify(id: Int, notification: Notification){
|
||||||
|
notificationManager.notify(id, notification)
|
||||||
|
}
|
||||||
|
|
||||||
fun updateDownloadNotification(
|
fun updateDownloadNotification(
|
||||||
id: Int,
|
id: Int,
|
||||||
desc: String,
|
desc: String,
|
||||||
|
|
@ -344,6 +369,19 @@ class NotificationUtil(var context: Context) {
|
||||||
fun createYTDLUpdateNotification() : Notification{
|
fun createYTDLUpdateNotification() : Notification{
|
||||||
val notificationBuilder = getBuilder(DOWNLOAD_MISC_CHANNEL_ID)
|
val notificationBuilder = getBuilder(DOWNLOAD_MISC_CHANNEL_ID)
|
||||||
notificationBuilder.setContentTitle("Updating YT-DLP...")
|
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()
|
return notificationBuilder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,12 @@ import android.content.ClipboardManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.graphics.drawable.ShapeDrawable
|
||||||
|
import android.graphics.drawable.shapes.OvalShape
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
|
import android.text.format.DateFormat
|
||||||
import android.util.DisplayMetrics
|
import android.util.DisplayMetrics
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
|
|
@ -28,18 +31,26 @@ import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.widget.doOnTextChanged
|
import androidx.core.widget.doOnTextChanged
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
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.Format
|
||||||
import com.deniscerri.ytdlnis.database.models.TemplateShortcut
|
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.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.BottomSheetBehavior
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
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.card.MaterialCardView
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import com.google.android.material.chip.ChipGroup
|
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 com.google.android.material.timepicker.TimeFormat
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import me.zhanghai.android.fastscroll.FastScrollerBuilder
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
object UiUtil {
|
object UiUtil {
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
|
|
@ -280,7 +294,7 @@ object UiUtil {
|
||||||
if (! file.exists()) return@forEach
|
if (! file.exists()) return@forEach
|
||||||
val uri = FileProvider.getUriForFile(
|
val uri = FileProvider.getUriForFile(
|
||||||
fragmentContext,
|
fragmentContext,
|
||||||
"com.deniscerri.ytdl.fileprovider",
|
fragmentContext.packageName + ".fileprovider",
|
||||||
file
|
file
|
||||||
)
|
)
|
||||||
uris.add(uri)
|
uris.add(uri)
|
||||||
|
|
@ -336,6 +350,150 @@ object UiUtil {
|
||||||
datePicker.show(fragmentManager, "datepicker")
|
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){
|
fun showFormatDetails(format: Format, activity: Activity){
|
||||||
val bottomSheet = BottomSheetDialog(activity)
|
val bottomSheet = BottomSheetDialog(activity)
|
||||||
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
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")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
fun RecyclerView.forceFastScrollMode()
|
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){
|
fun EditText.setTextAndRecalculateWidth(t : String){
|
||||||
|
val scale = context.resources.displayMetrics.density
|
||||||
this.setText(t)
|
this.setText(t)
|
||||||
val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
|
val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
|
||||||
val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
|
val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
|
||||||
this.measure(widthMeasureSpec, heightMeasureSpec)
|
this.measure(widthMeasureSpec, heightMeasureSpec)
|
||||||
val requiredWidth: Int = this.measuredWidth
|
val requiredWidth: Int = this.measuredWidth
|
||||||
if (t.isEmpty()){
|
if (t.length < 5){
|
||||||
this.layoutParams.width = 70
|
this.layoutParams.width = (70 * scale + 0.5F).toInt()
|
||||||
}else{
|
}else{
|
||||||
this.layoutParams.width = requiredWidth
|
this.layoutParams.width = requiredWidth
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,27 +3,15 @@ package com.deniscerri.ytdlnis.util
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.media3.common.AudioAttributes
|
import androidx.media3.common.AudioAttributes
|
||||||
import androidx.media3.common.C
|
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.DefaultLoadControl
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
|
||||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
|
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
|
||||||
import com.deniscerri.ytdlnis.App
|
|
||||||
import org.chromium.net.CronetEngine
|
|
||||||
import java.util.concurrent.Executors
|
|
||||||
|
|
||||||
object VideoPlayerUtil {
|
object VideoPlayerUtil {
|
||||||
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
||||||
fun buildPlayer(context: Context) : ExoPlayer {
|
fun buildPlayer(context: Context) : ExoPlayer {
|
||||||
val player: 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 trackSelector = DefaultTrackSelector(context)
|
||||||
val loadControl = DefaultLoadControl.Builder()
|
val loadControl = DefaultLoadControl.Builder()
|
||||||
|
|
@ -37,15 +25,8 @@ object VideoPlayerUtil {
|
||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val cronetDataSourceFactory = CronetDataSource.Factory(
|
|
||||||
cronetEngine,
|
|
||||||
Executors.newCachedThreadPool()
|
|
||||||
)
|
|
||||||
val dataSourceFactory = DefaultDataSource.Factory(context, cronetDataSourceFactory)
|
|
||||||
|
|
||||||
player = ExoPlayer.Builder(context)
|
player = ExoPlayer.Builder(context)
|
||||||
.setUsePlatformDiagnostics(false)
|
.setUsePlatformDiagnostics(false)
|
||||||
.setMediaSourceFactory(DefaultMediaSourceFactory(dataSourceFactory))
|
|
||||||
.setTrackSelector(trackSelector)
|
.setTrackSelector(trackSelector)
|
||||||
.setLoadControl(loadControl)
|
.setLoadControl(loadControl)
|
||||||
.setHandleAudioBecomingNoisy(true)
|
.setHandleAudioBecomingNoisy(true)
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,12 @@ import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.navigation.NavDeepLinkBuilder
|
import androidx.navigation.NavDeepLinkBuilder
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.work.Data
|
import androidx.work.CoroutineWorker
|
||||||
import androidx.work.ForegroundInfo
|
import androidx.work.ForegroundInfo
|
||||||
import androidx.work.Worker
|
import androidx.work.WorkInfo
|
||||||
|
import androidx.work.WorkManager
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
|
import androidx.work.await
|
||||||
import androidx.work.workDataOf
|
import androidx.work.workDataOf
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.DBManager
|
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.models.LogItem
|
||||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
import com.deniscerri.ytdlnis.database.repository.LogRepository
|
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.FileUtil
|
||||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||||
import com.yausername.youtubedl_android.YoutubeDL
|
import com.yausername.youtubedl_android.YoutubeDL
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
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.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
|
||||||
class DownloadWorker(
|
class DownloadWorker(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
workerParams: WorkerParameters
|
workerParams: WorkerParameters
|
||||||
) : Worker(context, workerParams) {
|
) : CoroutineWorker(context, workerParams) {
|
||||||
override fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
itemId = inputData.getLong("id", 0)
|
if (isStopped) return Result.success()
|
||||||
if (itemId == 0L || isStopped) return Result.success()
|
|
||||||
|
|
||||||
val notificationUtil = NotificationUtil(context)
|
val notificationUtil = NotificationUtil(context)
|
||||||
val infoUtil = InfoUtil(context)
|
val infoUtil = InfoUtil(context)
|
||||||
val dbManager = DBManager.getInstance(context)
|
val dbManager = DBManager.getInstance(context)
|
||||||
val dao = dbManager.downloadDao
|
val dao = dbManager.downloadDao
|
||||||
val repository = DownloadRepository(dao)
|
|
||||||
val historyDao = dbManager.historyDao
|
val historyDao = dbManager.historyDao
|
||||||
val resultDao = dbManager.resultDao
|
val resultDao = dbManager.resultDao
|
||||||
val logRepo = LogRepository(dbManager.logDao)
|
val logRepo = LogRepository(dbManager.logDao)
|
||||||
val handler = Handler(Looper.getMainLooper())
|
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?
|
runningYTDLInstances.addAll(dao.getActiveDownloadsList().map { it.id })
|
||||||
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()
|
|
||||||
|
|
||||||
val pendingIntent = NavDeepLinkBuilder(context)
|
val pendingIntent = NavDeepLinkBuilder(context)
|
||||||
.setGraph(R.navigation.nav_graph)
|
.setGraph(R.navigation.nav_graph)
|
||||||
.setDestination(R.id.downloadQueueMainFragment)
|
.setDestination(R.id.downloadQueueMainFragment)
|
||||||
.createPendingIntent()
|
.createPendingIntent()
|
||||||
|
|
||||||
val notification = notificationUtil.createDownloadServiceNotification(pendingIntent, downloadItem.title, downloadItem.id.toInt(), NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID)
|
var notification = notificationUtil.createDefaultWorkerNotification()
|
||||||
val foregroundInfo = ForegroundInfo(downloadItem.id.toInt(), notification)
|
val foregroundInfo = ForegroundInfo(Random.nextInt(1000000000), notification)
|
||||||
setForegroundAsync(foregroundInfo)
|
setForegroundAsync(foregroundInfo)
|
||||||
|
|
||||||
Log.e(TAG, downloadItem.toString())
|
queuedItems.collectLatest { items ->
|
||||||
|
val running = ArrayList(runningYTDLInstances)
|
||||||
runBlocking{
|
if (items.isEmpty() && running.isEmpty()) WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id)
|
||||||
YoutubeDL.getInstance().destroyProcessById(itemId.toString())
|
val concurrentDownloads = sharedPreferences.getInt("concurrent_downloads", 1) - running.size
|
||||||
repository.setDownloadStatus(downloadItem, DownloadRepository.Status.Active)
|
val eligibleDownloads = items.take(if (concurrentDownloads < 0) 0 else concurrentDownloads).filter { it.id !in running }
|
||||||
}
|
eligibleDownloads.forEach {downloadItem ->
|
||||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
runningYTDLInstances.add(downloadItem.id)
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
withContext(Dispatchers.Main){
|
||||||
//update item if its incomplete
|
notification = notificationUtil.createDownloadServiceNotification(pendingIntent, downloadItem.title, downloadItem.id.toInt(), NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID)
|
||||||
updateDownloadItem(downloadItem, infoUtil, dao, resultDao)
|
notificationUtil.notify(downloadItem.id.toInt(), notification)
|
||||||
}
|
}
|
||||||
|
val request = infoUtil.buildYoutubeDLRequest(downloadItem)
|
||||||
val request = infoUtil.buildYoutubeDLRequest(downloadItem)
|
downloadItem.status = DownloadRepository.Status.Active.toString()
|
||||||
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){
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
logRepo.update(line, logItem.id)
|
//update item if its incomplete
|
||||||
}
|
updateDownloadItem(downloadItem, infoUtil, dao, resultDao)
|
||||||
}
|
|
||||||
}
|
|
||||||
}.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))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (finalPaths.isNotEmpty()){
|
val cacheDir = FileUtil.getCachePath()
|
||||||
setProgressAsync(workDataOf("progress" to 100, "output" to "Moved file to $downloadLocation", "id" to downloadItem.id))
|
val tempFileDir = File(cacheDir, downloadItem.id.toString())
|
||||||
}else{
|
tempFileDir.delete()
|
||||||
finalPaths = listOf(context.getString(R.string.unfound_file))
|
tempFileDir.mkdirs()
|
||||||
}
|
|
||||||
}catch (e: Exception){
|
val downloadLocation = downloadItem.downloadPath
|
||||||
finalPaths = listOf(context.getString(R.string.unfound_file))
|
val keepCache = sharedPreferences.getBoolean("keep_cache", false)
|
||||||
e.printStackTrace()
|
val logDownloads = sharedPreferences.getBoolean("log_downloads", false) && !sharedPreferences.getBoolean("incognito", false)
|
||||||
handler.postDelayed({
|
|
||||||
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
|
val logItem = LogItem(
|
||||||
}, 1000)
|
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
|
if (logDownloads){
|
||||||
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 {
|
runBlocking {
|
||||||
infoUtil.getFromYTDL(downloadItem.url).forEach { res ->
|
logItem.id = logRepo.insert(logItem)
|
||||||
if (res != null) {
|
downloadItem.logID = logItem.id
|
||||||
resultDao.insert(res)
|
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 {
|
//put download in history
|
||||||
if (it is YoutubeDL.CanceledException) {
|
val incognito = sharedPreferences.getBoolean("incognito", false)
|
||||||
return Result.success(
|
if (!incognito) {
|
||||||
Data.Builder().putString("output", "Download has been cancelled!").build()
|
val unixtime = System.currentTimeMillis() / 1000
|
||||||
)
|
val file = File(finalPaths?.first()!!)
|
||||||
}else{
|
downloadItem.format.filesize = file.length()
|
||||||
if (logDownloads){
|
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)
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
historyDao.insert(historyItem)
|
||||||
if(it.message != null){
|
}
|
||||||
logRepo.update(it.message!!, logItem.id)
|
|
||||||
|
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()
|
if (eligibleDownloads.isNotEmpty()){
|
||||||
handler.postDelayed({
|
eligibleDownloads.forEach { it.status = DownloadRepository.Status.Active.toString() }
|
||||||
Toast.makeText(context, it.message, Toast.LENGTH_LONG).show()
|
dao.updateMultiple(eligibleDownloads)
|
||||||
}, 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()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return Result.success()
|
return Result.success()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -242,7 +265,7 @@ class DownloadWorker(
|
||||||
var wasQuickDownloaded = false
|
var wasQuickDownloaded = false
|
||||||
if (downloadItem.title.isEmpty() || downloadItem.author.isEmpty() || downloadItem.thumb.isEmpty()){
|
if (downloadItem.title.isEmpty() || downloadItem.author.isEmpty() || downloadItem.thumb.isEmpty()){
|
||||||
runCatching {
|
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))
|
setProgressAsync(workDataOf("progress" to 0, "output" to context.getString(R.string.updating_download_data), "id" to downloadItem.id))
|
||||||
val info = infoUtil.getMissingInfo(downloadItem.url)
|
val info = infoUtil.getMissingInfo(downloadItem.url)
|
||||||
if (downloadItem.title.isEmpty()) downloadItem.title = info?.title.toString()
|
if (downloadItem.title.isEmpty()) downloadItem.title = info?.title.toString()
|
||||||
|
|
@ -259,17 +282,10 @@ class DownloadWorker(
|
||||||
return wasQuickDownloaded
|
return wasQuickDownloaded
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStopped() {
|
|
||||||
destroyYTDLProcess()
|
|
||||||
super.onStopped()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun destroyYTDLProcess(){
|
|
||||||
YoutubeDL.getInstance().destroyProcessById(itemId.toInt().toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
var itemId: Long = 0
|
val runningYTDLInstances: MutableList<Long> = mutableListOf()
|
||||||
const val TAG = "DownloadWorker"
|
const val TAG = "DownloadWorker"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,11 @@ import android.os.Looper
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
import androidx.work.CoroutineWorker
|
||||||
import androidx.work.ForegroundInfo
|
import androidx.work.ForegroundInfo
|
||||||
import androidx.work.Worker
|
import androidx.work.Worker
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import androidx.work.workDataOf
|
import androidx.work.workDataOf
|
||||||
import com.deniscerri.ytdlnis.MainActivity
|
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.DBManager
|
import com.deniscerri.ytdlnis.database.DBManager
|
||||||
import com.deniscerri.ytdlnis.database.models.Format
|
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.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdlnis.ui.more.TerminalActivity
|
import com.deniscerri.ytdlnis.ui.more.TerminalActivity
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||||
import com.yausername.youtubedl_android.YoutubeDL
|
import com.yausername.youtubedl_android.YoutubeDL
|
||||||
import com.yausername.youtubedl_android.YoutubeDLRequest
|
import com.yausername.youtubedl_android.YoutubeDLRequest
|
||||||
|
|
@ -29,17 +30,14 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.time.LocalDate
|
|
||||||
import java.time.LocalDateTime
|
|
||||||
import java.util.Calendar
|
|
||||||
|
|
||||||
|
|
||||||
class TerminalDownloadWorker(
|
class TerminalDownloadWorker(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
workerParams: WorkerParameters
|
workerParams: WorkerParameters
|
||||||
) : Worker(context, workerParams) {
|
) : CoroutineWorker(context, workerParams) {
|
||||||
override fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
val itemId = inputData.getInt("id", 0)
|
itemId = inputData.getInt("id", 0)
|
||||||
val command = inputData.getString("command")
|
val command = inputData.getString("command")
|
||||||
if (itemId == 0) return Result.failure()
|
if (itemId == 0) return Result.failure()
|
||||||
if (command!!.isEmpty()) return Result.failure()
|
if (command!!.isEmpty()) return Result.failure()
|
||||||
|
|
@ -49,6 +47,7 @@ class TerminalDownloadWorker(
|
||||||
|
|
||||||
val notificationUtil = NotificationUtil(context)
|
val notificationUtil = NotificationUtil(context)
|
||||||
val handler = Handler(Looper.getMainLooper())
|
val handler = Handler(Looper.getMainLooper())
|
||||||
|
val infoUtil = InfoUtil(context)
|
||||||
|
|
||||||
val intent = Intent(context, TerminalActivity::class.java)
|
val intent = Intent(context, TerminalActivity::class.java)
|
||||||
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
|
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
|
||||||
|
|
@ -59,27 +58,26 @@ class TerminalDownloadWorker(
|
||||||
val request = YoutubeDLRequest(emptyList())
|
val request = YoutubeDLRequest(emptyList())
|
||||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
|
||||||
val downloadLocation = sharedPreferences.getString("command_path", context.getString(R.string.command_path))
|
val downloadLocation = sharedPreferences.getString("command_path", FileUtil.getDefaultCommandPath())
|
||||||
val tempFileDir = File(context.cacheDir.absolutePath + "/downloads/" + itemId)
|
|
||||||
tempFileDir.delete()
|
|
||||||
tempFileDir.mkdirs()
|
|
||||||
|
|
||||||
val outputFile = File(context.cacheDir.absolutePath + "/$itemId.txt")
|
val outputFile = File(context.cacheDir.absolutePath + "/$itemId.txt")
|
||||||
if (! outputFile.exists()) outputFile.createNewFile()
|
if (! outputFile.exists()) outputFile.createNewFile()
|
||||||
|
|
||||||
request.addOption(
|
request.addOption(
|
||||||
"--config-locations",
|
"--config-locations",
|
||||||
File(context.cacheDir.absolutePath + "/downloads/config${System.currentTimeMillis()}.txt").apply {
|
File(context.cacheDir.absolutePath + "/config${System.currentTimeMillis()}.txt").apply {
|
||||||
writeText(command)
|
writeText(command)
|
||||||
}.absolutePath
|
}.absolutePath
|
||||||
)
|
)
|
||||||
|
|
||||||
val cookiesFile = File(context.cacheDir, "cookies.txt")
|
if (sharedPreferences.getBoolean("use_cookies", false)){
|
||||||
if (cookiesFile.exists()){
|
val cookiesFile = File(context.cacheDir, "cookies.txt")
|
||||||
request.addOption("--cookies", cookiesFile.absolutePath)
|
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)
|
val logDownloads = sharedPreferences.getBoolean("log_downloads", false) && !sharedPreferences.getBoolean("incognito", false)
|
||||||
|
|
||||||
|
|
@ -88,7 +86,7 @@ class TerminalDownloadWorker(
|
||||||
"Terminal Download",
|
"Terminal Download",
|
||||||
"Downloading:\n" +
|
"Downloading:\n" +
|
||||||
"Terminal Download\n" +
|
"Terminal Download\n" +
|
||||||
"Command: ${command}\n\n",
|
"Command: ${infoUtil.parseYTDLRequestString(request)}\n\n",
|
||||||
Format(),
|
Format(),
|
||||||
DownloadViewModel.Type.command,
|
DownloadViewModel.Type.command,
|
||||||
System.currentTimeMillis(),
|
System.currentTimeMillis(),
|
||||||
|
|
@ -119,7 +117,7 @@ class TerminalDownloadWorker(
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
//move file from internal to set download directory
|
//move file from internal to set download directory
|
||||||
try {
|
try {
|
||||||
FileUtil.moveFile(tempFileDir.absoluteFile,context, downloadLocation!!, false){ p ->
|
FileUtil.moveFile(File(FileUtil.getDefaultCommandPath() + "/" + itemId),context, downloadLocation!!, false){ p ->
|
||||||
setProgressAsync(workDataOf("progress" to p))
|
setProgressAsync(workDataOf("progress" to p))
|
||||||
}
|
}
|
||||||
}catch (e: Exception){
|
}catch (e: Exception){
|
||||||
|
|
@ -130,12 +128,10 @@ class TerminalDownloadWorker(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (it.out.length > 200){
|
outputFile.appendText("${it.out}\n")
|
||||||
outputFile.appendText("${it.out}\n")
|
if (logDownloads){
|
||||||
if (logDownloads){
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
logRepo.update(it.out, logItem.id)
|
||||||
logRepo.update(it.out, logItem.id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
notificationUtil.cancelDownloadNotification(itemId)
|
notificationUtil.cancelDownloadNotification(itemId)
|
||||||
|
|
@ -144,7 +140,7 @@ class TerminalDownloadWorker(
|
||||||
if (it is YoutubeDL.CanceledException) {
|
if (it is YoutubeDL.CanceledException) {
|
||||||
return Result.failure()
|
return Result.failure()
|
||||||
}
|
}
|
||||||
outputFile.appendText("${it.message}\n")
|
outputFile.appendText("\n${it.message}\n")
|
||||||
if (logDownloads){
|
if (logDownloads){
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
if (it.message != null){
|
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)
|
Log.e(TAG, context.getString(R.string.failed_download), it)
|
||||||
notificationUtil.cancelDownloadNotification(itemId)
|
notificationUtil.cancelDownloadNotification(itemId)
|
||||||
|
|
@ -163,12 +159,9 @@ class TerminalDownloadWorker(
|
||||||
return Result.success()
|
return Result.success()
|
||||||
|
|
||||||
}
|
}
|
||||||
override fun onStopped() {
|
|
||||||
YoutubeDL.getInstance().destroyProcessById(DownloadWorker.itemId.toInt().toString())
|
|
||||||
super.onStopped()
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private var itemId : Int = 0
|
||||||
const val TAG = "DownloadWorker"
|
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"
|
<vector android:height="24dp"
|
||||||
android:viewportHeight="24" android:viewportWidth="24"
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
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>
|
</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:orientation="vertical"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:scrollbars="none"
|
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:clipToPadding="false"
|
||||||
android:paddingBottom="100dp"
|
android:paddingBottom="100dp"
|
||||||
app:spanCount="3"
|
app:spanCount="3"
|
||||||
|
|
|
||||||
|
|
@ -171,12 +171,6 @@
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:scrollbars="none"
|
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:clipToPadding="false"
|
||||||
android:paddingBottom="100dp"
|
android:paddingBottom="100dp"
|
||||||
app:spanCount="2"
|
app:spanCount="2"
|
||||||
|
|
|
||||||
|
|
@ -156,11 +156,6 @@
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:scrollbars="none"
|
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:clipToPadding="false"
|
||||||
android:paddingBottom="100dp"
|
android:paddingBottom="100dp"
|
||||||
app:spanCount="4"
|
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.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>
|
</LinearLayout>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
@ -183,7 +183,6 @@
|
||||||
android:minHeight="0dp"
|
android:minHeight="0dp"
|
||||||
android:padding="0dp"
|
android:padding="0dp"
|
||||||
app:cornerRadius="10dp"
|
app:cornerRadius="10dp"
|
||||||
app:icon="@drawable/ic_video"
|
|
||||||
app:iconTint="?attr/colorAccent"
|
app:iconTint="?attr/colorAccent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
|
|
||||||
|
|
@ -92,11 +92,6 @@
|
||||||
android:paddingTop="10dp"
|
android:paddingTop="10dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
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"
|
android:paddingBottom="70dp"
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -104,6 +104,7 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:fontFamily="monospace"
|
android:fontFamily="monospace"
|
||||||
|
android:textSize="14sp"
|
||||||
android:inputType="textMultiLine"
|
android:inputType="textMultiLine"
|
||||||
android:maxLines="2000" />
|
android:maxLines="2000" />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,13 @@
|
||||||
|
|
||||||
<LinearLayout android:layout_width="match_parent"
|
<LinearLayout android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingBottom="20dp"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:text="@string/format"
|
android:text="@string/format"
|
||||||
android:textSize="14sp"
|
android:textSize="17sp"
|
||||||
android:layout_margin="20dp"
|
android:layout_margin="20dp"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
|
@ -22,22 +23,24 @@
|
||||||
android:paddingHorizontal="10dp"
|
android:paddingHorizontal="10dp"
|
||||||
android:weightSum="100"
|
android:weightSum="100"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
android:orientation="horizontal" >
|
android:orientation="vertical" >
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_weight="50"
|
|
||||||
android:text="@string/format_id"
|
android:text="@string/format_id"
|
||||||
android:textSize="18sp"
|
android:textSize="14sp"
|
||||||
android:layout_margin="10dp"
|
android:textStyle="bold"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
|
android:layout_marginVertical="5dp"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/format_id_value"
|
android:id="@+id/format_id_value"
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_weight="50"
|
|
||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
android:layout_margin="10dp"
|
android:layout_marginHorizontal="10dp"
|
||||||
android:gravity="end"
|
android:gravity="end"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
|
@ -51,25 +54,26 @@
|
||||||
android:paddingHorizontal="10dp"
|
android:paddingHorizontal="10dp"
|
||||||
android:weightSum="100"
|
android:weightSum="100"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
android:orientation="horizontal" >
|
android:orientation="vertical" >
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_weight="50"
|
android:textStyle="bold"
|
||||||
|
android:textSize="14sp"
|
||||||
|
|
||||||
android:text="@string/url"
|
android:text="@string/url"
|
||||||
android:textSize="18sp"
|
android:layout_marginHorizontal="10dp"
|
||||||
android:layout_margin="10dp"
|
android:layout_marginVertical="5dp"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/format_url_value"
|
android:id="@+id/format_url_value"
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:maxLines="1"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:layout_weight="50"
|
|
||||||
android:textSize="18sp"
|
|
||||||
android:layout_margin="10dp"
|
|
||||||
android:gravity="end"
|
android:gravity="end"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
@ -83,22 +87,23 @@
|
||||||
android:weightSum="100"
|
android:weightSum="100"
|
||||||
android:paddingHorizontal="10dp"
|
android:paddingHorizontal="10dp"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
android:orientation="horizontal" >
|
android:orientation="vertical" >
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_weight="50"
|
android:textStyle="bold"
|
||||||
android:textSize="18sp"
|
android:textSize="14sp"
|
||||||
|
|
||||||
android:text="@string/container"
|
android:text="@string/container"
|
||||||
android:layout_margin="10dp"
|
android:layout_marginHorizontal="10dp"
|
||||||
|
android:layout_marginVertical="5dp"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/container_value"
|
android:id="@+id/container_value"
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_weight="50"
|
|
||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
android:layout_margin="10dp"
|
android:layout_marginHorizontal="10dp"
|
||||||
android:gravity="end"
|
android:gravity="end"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
|
@ -112,22 +117,22 @@
|
||||||
android:weightSum="100"
|
android:weightSum="100"
|
||||||
android:paddingHorizontal="10dp"
|
android:paddingHorizontal="10dp"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
android:orientation="horizontal" >
|
android:orientation="vertical" >
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_weight="50"
|
android:textStyle="bold"
|
||||||
android:textSize="18sp"
|
android:textSize="14sp"
|
||||||
android:text="@string/codec"
|
android:text="@string/codec"
|
||||||
android:layout_margin="10dp"
|
android:layout_marginHorizontal="10dp"
|
||||||
|
android:layout_marginVertical="5dp"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/codec_value"
|
android:id="@+id/codec_value"
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_weight="50"
|
|
||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
android:layout_margin="10dp"
|
android:layout_marginHorizontal="10dp"
|
||||||
android:gravity="end"
|
android:gravity="end"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
|
@ -141,22 +146,22 @@
|
||||||
android:weightSum="100"
|
android:weightSum="100"
|
||||||
android:paddingHorizontal="10dp"
|
android:paddingHorizontal="10dp"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
android:orientation="horizontal" >
|
android:orientation="vertical" >
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_weight="50"
|
android:textStyle="bold"
|
||||||
|
android:textSize="14sp"
|
||||||
android:text="@string/file_size"
|
android:text="@string/file_size"
|
||||||
android:textSize="18sp"
|
android:layout_marginHorizontal="10dp"
|
||||||
android:layout_margin="10dp"
|
android:layout_marginVertical="5dp"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/filesize_value"
|
android:id="@+id/filesize_value"
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_weight="50"
|
|
||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
android:layout_margin="10dp"
|
android:layout_marginHorizontal="10dp"
|
||||||
android:gravity="end"
|
android:gravity="end"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
|
@ -170,22 +175,22 @@
|
||||||
android:weightSum="100"
|
android:weightSum="100"
|
||||||
android:paddingHorizontal="10dp"
|
android:paddingHorizontal="10dp"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
android:orientation="horizontal" >
|
android:orientation="vertical" >
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textSize="14sp"
|
||||||
android:text="@string/quality"
|
android:text="@string/quality"
|
||||||
android:layout_weight="50"
|
android:layout_marginHorizontal="10dp"
|
||||||
android:textSize="18sp"
|
android:layout_marginVertical="5dp"
|
||||||
android:layout_margin="10dp"
|
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/format_note_value"
|
android:id="@+id/format_note_value"
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_weight="50"
|
|
||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
android:layout_margin="10dp"
|
android:layout_marginHorizontal="10dp"
|
||||||
android:gravity="end"
|
android:gravity="end"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
|
@ -199,22 +204,22 @@
|
||||||
android:weightSum="100"
|
android:weightSum="100"
|
||||||
android:paddingHorizontal="10dp"
|
android:paddingHorizontal="10dp"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
android:orientation="horizontal" >
|
android:orientation="vertical" >
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_weight="50"
|
android:textStyle="bold"
|
||||||
|
android:textSize="14sp"
|
||||||
android:text="FPS"
|
android:text="FPS"
|
||||||
android:textSize="18sp"
|
android:layout_marginHorizontal="10dp"
|
||||||
android:layout_margin="10dp"
|
android:layout_marginVertical="5dp"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/fps_value"
|
android:id="@+id/fps_value"
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_weight="50"
|
|
||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
android:layout_margin="10dp"
|
android:layout_marginHorizontal="10dp"
|
||||||
android:gravity="end"
|
android:gravity="end"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
|
@ -228,22 +233,22 @@
|
||||||
android:weightSum="100"
|
android:weightSum="100"
|
||||||
android:paddingHorizontal="10dp"
|
android:paddingHorizontal="10dp"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
android:orientation="horizontal" >
|
android:orientation="vertical" >
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_weight="50"
|
android:textStyle="bold"
|
||||||
|
android:textSize="14sp"
|
||||||
android:text="@string/audio_samplerate"
|
android:text="@string/audio_samplerate"
|
||||||
android:textSize="18sp"
|
android:layout_marginHorizontal="10dp"
|
||||||
android:layout_margin="10dp"
|
android:layout_marginVertical="5dp"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/asr_value"
|
android:id="@+id/asr_value"
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_weight="50"
|
|
||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
android:layout_margin="10dp"
|
android:layout_marginHorizontal="10dp"
|
||||||
android:gravity="end"
|
android:gravity="end"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/pause_resume"
|
android:id="@+id/pause_resume"
|
||||||
android:visibility="visible"
|
android:visibility="gone"
|
||||||
style="@style/Widget.Material3.Button.TextButton"
|
style="@style/Widget.Material3.Button.TextButton"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|
|
||||||
|
|
@ -110,131 +110,7 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
<LinearLayout
|
<include layout="@layout/adjust_audio" />
|
||||||
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>
|
|
||||||
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
|
||||||
|
|
@ -69,50 +69,7 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<include layout="@layout/adjust_command" />
|
||||||
<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>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -110,209 +110,7 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<include layout="@layout/adjust_video" />
|
||||||
<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>
|
|
||||||
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
|
||||||
|
|
@ -112,11 +112,6 @@
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:scrollbars="none"
|
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:clipToPadding="false"
|
||||||
android:paddingBottom="100dp"
|
android:paddingBottom="100dp"
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager">
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager">
|
||||||
|
|
|
||||||
|
|
@ -133,14 +133,8 @@
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:scrollbars="none"
|
android:scrollbars="none"
|
||||||
|
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:paddingBottom="100dp"
|
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"
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
|
@ -23,14 +24,7 @@
|
||||||
android:id="@+id/download_recyclerview"
|
android:id="@+id/download_recyclerview"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:fastScrollEnabled="true"
|
|
||||||
android:scrollbars="none"
|
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_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintHorizontal_bias="0.0"
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
|
@ -38,5 +32,5 @@
|
||||||
|
|
||||||
</androidx.recyclerview.widget.RecyclerView>
|
</androidx.recyclerview.widget.RecyclerView>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,8 @@
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/download_card_view"
|
android:id="@+id/download_card_view"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:padding="10dp"
|
android:paddingHorizontal="10dp"
|
||||||
|
android:paddingVertical="3dp"
|
||||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
|
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:cardPreventCornerOverlap="true"
|
app:cardPreventCornerOverlap="true"
|
||||||
|
|
@ -32,7 +33,7 @@
|
||||||
android:clickable="false"
|
android:clickable="false"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:layout_constraintHorizontal_weight="0.3"
|
app:layout_constraintHorizontal_weight="0.4"
|
||||||
android:adjustViewBounds="true"
|
android:adjustViewBounds="true"
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
|
@ -81,11 +82,9 @@
|
||||||
android:maxLines="2"
|
android:maxLines="2"
|
||||||
android:paddingHorizontal="10dp"
|
android:paddingHorizontal="10dp"
|
||||||
android:scrollbars="none"
|
android:scrollbars="none"
|
||||||
android:shadowColor="@color/black"
|
|
||||||
android:shadowDx="4"
|
android:shadowDx="4"
|
||||||
android:shadowDy="4"
|
android:shadowDy="4"
|
||||||
android:shadowRadius="2"
|
android:shadowRadius="2"
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="15sp"
|
android:textSize="15sp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
|
@ -93,6 +92,22 @@
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
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>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,22 +39,6 @@
|
||||||
app:layout_constraintTop_toTopOf="@+id/frame_layout"
|
app:layout_constraintTop_toTopOf="@+id/frame_layout"
|
||||||
app:layout_constraintEnd_toEndOf="@+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
|
<androidx.core.widget.NestedScrollView
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
|
|
@ -72,13 +56,13 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_marginVertical="10dp">
|
android:layout_margin="10dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/title"
|
android:id="@+id/title"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_marginStart="20dp"
|
android:textStyle="bold"
|
||||||
android:layout_marginEnd="10dp"
|
android:layout_marginHorizontal="10dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:maxLines="2"
|
android:maxLines="2"
|
||||||
|
|
@ -93,14 +77,14 @@
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/bottom_info"
|
android:id="@+id/bottom_info"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_marginStart="20dp"
|
|
||||||
android:layout_marginEnd="10dp"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="4dp"
|
android:layout_marginHorizontal="10dp"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
|
app:layout_constraintVertical_bias="0.0"
|
||||||
android:singleLine="false"
|
android:singleLine="false"
|
||||||
android:text="@string/app_name"
|
android:text="@string/app_name"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/nestedScrollView"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/download_music"
|
app:layout_constraintEnd_toStartOf="@+id/download_music"
|
||||||
app:layout_constraintHorizontal_bias="0.0"
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
|
@ -136,6 +120,7 @@
|
||||||
|
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
<androidx.core.widget.NestedScrollView
|
||||||
|
android:id="@+id/nestedScrollView"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
|
@ -150,12 +135,12 @@
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/running"
|
android:id="@+id/running"
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_marginHorizontal="20dp"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginVertical="10dp"
|
android:layout_marginVertical="10dp"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
android:text="@string/running"
|
android:text="@string/running"
|
||||||
|
android:textStyle="bold"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
|
@ -170,12 +155,12 @@
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/queued"
|
android:id="@+id/queued"
|
||||||
android:layout_marginHorizontal="20dp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginVertical="10dp"
|
android:layout_marginVertical="10dp"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
android:text="@string/download_queue"
|
android:text="@string/download_queue"
|
||||||
|
android:textStyle="bold"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/running_recycler" />
|
app:layout_constraintTop_toBottomOf="@id/running_recycler" />
|
||||||
|
|
||||||
|
|
@ -189,13 +174,11 @@
|
||||||
app:layout_constraintTop_toBottomOf="@id/queued" />
|
app:layout_constraintTop_toBottomOf="@id/queued" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/bottom_sheet_link"
|
android:id="@+id/bottom_sheet_link"
|
||||||
style="@style/Widget.Material3.Button.TextButton.Icon"
|
style="@style/Widget.Material3.Button.TextButton.Icon"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginHorizontal="20dp"
|
|
||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="10dp"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
android:text="@string/app_name"
|
android:text="@string/app_name"
|
||||||
|
|
|
||||||
|
|
@ -23,12 +23,37 @@
|
||||||
app:menu="@menu/select_playlist_menu"
|
app:menu="@menu/select_playlist_menu"
|
||||||
android:layout_gravity="bottom">
|
android:layout_gravity="bottom">
|
||||||
|
|
||||||
<TextView
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/selected"
|
android:id="@+id/from_textinput"
|
||||||
android:layout_width="wrap_content"
|
style="@style/Widget.Material3.TextInputLayout.FilledBox"
|
||||||
android:textStyle="bold"
|
android:layout_width="70dp"
|
||||||
android:layout_marginHorizontal="30dp"
|
android:layout_height="wrap_content"
|
||||||
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>
|
</com.google.android.material.bottomappbar.BottomAppBar>
|
||||||
|
|
@ -36,12 +61,10 @@
|
||||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
android:id="@+id/check_all"
|
android:id="@+id/check_all"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:text="@string/select_all"
|
|
||||||
app:icon="@drawable/ic_select_all"
|
app:icon="@drawable/ic_select_all"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_anchor="@id/bottomAppBar"/>
|
app:layout_anchor="@id/bottomAppBar"/>
|
||||||
|
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -56,72 +79,42 @@
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginHorizontal="20dp"
|
android:orientation="horizontal">
|
||||||
android:orientation="horizontal"
|
|
||||||
android:paddingTop="20dp">
|
|
||||||
|
|
||||||
|
|
||||||
<TextView
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:id="@+id/bottom_sheet_title"
|
android:id="@+id/appbar"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="10dp"
|
|
||||||
android:text="@string/select"
|
|
||||||
android:textSize="20sp"
|
|
||||||
app:layout_constraintStart_toStartOf="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_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
|
<LinearLayout
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginHorizontal="20dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="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">
|
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>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
@ -136,11 +129,6 @@
|
||||||
android:focusableInTouchMode="true"
|
android:focusableInTouchMode="true"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingBottom="80dp"
|
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" />
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,6 @@
|
||||||
android:icon="@drawable/ic_format"
|
android:icon="@drawable/ic_format"
|
||||||
app:showAsAction="always"/>
|
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
|
<item
|
||||||
android:id="@+id/folder"
|
android:id="@+id/folder"
|
||||||
android:title="@string/directories"
|
android:title="@string/directories"
|
||||||
|
|
@ -29,5 +23,12 @@
|
||||||
app:showAsAction="always"/>
|
app:showAsAction="always"/>
|
||||||
|
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/more"
|
||||||
|
android:title="@string/more"
|
||||||
|
android:icon="@drawable/ic_more"
|
||||||
|
app:showAsAction="always"/>
|
||||||
|
|
||||||
|
|
||||||
</menu>
|
</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"
|
android:icon="@drawable/baseline_delete_24"
|
||||||
app:showAsAction="ifRoom" />
|
app:showAsAction="ifRoom" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/up"
|
||||||
|
android:icon="@drawable/ic_arrow_up"
|
||||||
|
app:showAsAction="ifRoom"
|
||||||
|
android:title="Move Up" />
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/download"
|
android:id="@+id/download"
|
||||||
android:title="@string/download"
|
android:title="@string/download"
|
||||||
android:icon="@drawable/baseline_download_24"
|
android:icon="@drawable/baseline_download_24"
|
||||||
app:showAsAction="ifRoom" />
|
app:showAsAction="ifRoom" />
|
||||||
|
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/select_all"
|
android:id="@+id/select_all"
|
||||||
android:title="@string/select_all"
|
android:title="@string/select_all"
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,5 @@
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
|
|
@ -2,4 +2,5 @@
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background_dark"/>
|
<background android:drawable="@drawable/ic_launcher_background_dark"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
|
|
@ -2,4 +2,5 @@
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background_light"/>
|
<background android:drawable="@drawable/ic_launcher_background_light"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
</adaptive-icon>
|
</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:id="@+id/homeFragment"
|
||||||
android:name="com.deniscerri.ytdlnis.ui.HomeFragment"
|
android:name="com.deniscerri.ytdlnis.ui.HomeFragment"
|
||||||
android:label="@string/app_name"
|
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
|
<fragment
|
||||||
android:id="@+id/historyFragment"
|
android:id="@+id/historyFragment"
|
||||||
android:name="com.deniscerri.ytdlnis.ui.downloads.HistoryFragment"
|
android:name="com.deniscerri.ytdlnis.ui.downloads.HistoryFragment"
|
||||||
|
|
@ -80,4 +84,8 @@
|
||||||
android:id="@+id/downloadLogFragment"
|
android:id="@+id/downloadLogFragment"
|
||||||
android:name="com.deniscerri.ytdlnis.ui.more.downloadLogs.DownloadLogFragment"
|
android:name="com.deniscerri.ytdlnis.ui.more.downloadLogs.DownloadLogFragment"
|
||||||
android:label="DownloadLogFragment" />
|
android:label="DownloadLogFragment" />
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/resultCardDetailsDialog"
|
||||||
|
android:name="com.deniscerri.ytdlnis.ui.downloadcard.ResultCardDetailsDialog"
|
||||||
|
android:label="ResultCardDetailsDialog" />
|
||||||
</navigation>
|
</navigation>
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<resources>
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
<string-array name="audio_containers">
|
<string-array name="audio_containers">
|
||||||
<item>@string/defaultValue</item>
|
<item>@string/defaultValue</item>
|
||||||
<item>mp3</item>
|
<item>mp3</item>
|
||||||
|
|
@ -65,12 +65,14 @@
|
||||||
<string-array name="format_ordering">
|
<string-array name="format_ordering">
|
||||||
<item>@string/file_size</item>
|
<item>@string/file_size</item>
|
||||||
<item>@string/container</item>
|
<item>@string/container</item>
|
||||||
|
<item>@string/codec</item>
|
||||||
<item>ID</item>
|
<item>ID</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<string-array name="format_ordering_values">
|
<string-array name="format_ordering_values">
|
||||||
<item>filesize</item>
|
<item>filesize</item>
|
||||||
<item>container</item>
|
<item>container</item>
|
||||||
|
<item>codec</item>
|
||||||
<item>id</item>
|
<item>id</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
|
@ -770,4 +772,22 @@
|
||||||
<item>queue</item>
|
<item>queue</item>
|
||||||
</string-array>
|
</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>
|
</resources>
|
||||||