other stuff
- formats auto updating as soon as the download card opens if auto-update is on - added preferred audio format always in the video tab - made app post downloads for queue in chunks - made app always save logs in case it fails, and if succeeds and logs are off it deletes it - fixed app navigating to home screen when cancelling download card in history screen - added a button to skip incoming app update so it wont bother you anymore - fixed settings not restoring some fields - fixed crunchyroll not working with cookies - added search for command templates - added sort filtering for command templates - added all shortcuts inside filename template creation dialog. Long click them to see the explanation - added preference to hide elements from the download card - made avc1 and m4a as preferred codecs for noobs - create dialog when download item exists. U can edit the download item right from there or access the history item to view the file
This commit is contained in:
parent
b636bf199c
commit
9353e08ee9
60 changed files with 1998 additions and 1100 deletions
|
|
@ -10,7 +10,7 @@ def properties = new Properties()
|
||||||
def versionMajor = 1
|
def versionMajor = 1
|
||||||
def versionMinor = 6
|
def versionMinor = 6
|
||||||
def versionPatch = 8
|
def versionPatch = 8
|
||||||
def versionBuild = 2 // bump for dogfood builds, public betas, etc.
|
def versionBuild = 3 // bump for dogfood builds, public betas, etc.
|
||||||
def versionExt = ""
|
def versionExt = ""
|
||||||
|
|
||||||
if (versionBuild > 0){
|
if (versionBuild > 0){
|
||||||
|
|
@ -134,7 +134,7 @@ dependencies {
|
||||||
implementation 'androidx.preference:preference-ktx: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.12.0'
|
||||||
implementation 'androidx.test.ext:junit-ktx:1.1.5'
|
implementation 'androidx.test.ext:junit-ktx:1.1.5'
|
||||||
implementation 'androidx.compose.ui:ui-android:1.5.3'
|
implementation 'androidx.compose.ui:ui-android:1.5.3'
|
||||||
testImplementation "junit:junit:$junitVer"
|
testImplementation "junit:junit:$junitVer"
|
||||||
|
|
@ -157,10 +157,10 @@ 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"
|
||||||
ksp "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.paging:paging-runtime-ktx:3.2.1'
|
||||||
implementation "androidx.room:room-paging:2.5.2"
|
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.2'
|
||||||
implementation "androidx.compose.runtime:runtime:$composeVer"
|
implementation "androidx.compose.runtime:runtime:$composeVer"
|
||||||
androidTestImplementation 'com.google.truth:truth:1.1.5'
|
androidTestImplementation 'com.google.truth:truth:1.1.5'
|
||||||
|
|
||||||
|
|
@ -172,7 +172,7 @@ dependencies {
|
||||||
|
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0'
|
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0'
|
||||||
implementation 'it.xabaras.android:recyclerview-swipedecorator:1.4'
|
implementation 'it.xabaras.android:recyclerview-swipedecorator:1.4'
|
||||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.1"
|
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.2"
|
||||||
implementation "com.github.Irineu333:Highlight-KT:1.0.4"
|
implementation "com.github.Irineu333:Highlight-KT:1.0.4"
|
||||||
|
|
||||||
// For media playback using ExoPlayer
|
// For media playback using ExoPlayer
|
||||||
|
|
@ -188,5 +188,5 @@ dependencies {
|
||||||
implementation "com.anggrayudi:storage:1.5.5"
|
implementation "com.anggrayudi:storage:1.5.5"
|
||||||
implementation 'me.zhanghai.android.fastscroll:library:1.3.0'
|
implementation 'me.zhanghai.android.fastscroll:library:1.3.0'
|
||||||
implementation 'com.google.accompanist:accompanist-webview:0.30.1'
|
implementation 'com.google.accompanist:accompanist-webview:0.30.1'
|
||||||
implementation 'androidx.compose.material3:material3-android:1.2.0-alpha08'
|
implementation 'androidx.compose.material3:material3-android:1.2.0-alpha09'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
13
app/proguard-rules.pro
vendored
13
app/proguard-rules.pro
vendored
|
|
@ -77,3 +77,16 @@
|
||||||
# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher.
|
# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher.
|
||||||
-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
|
-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
|
||||||
-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
|
-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
|
||||||
|
|
||||||
|
-keep class com.deniscerri.** { *; }
|
||||||
|
-keep class org.apache.commons.compress.archivers.zip.** { *; }
|
||||||
|
|
||||||
|
# Keep `Companion` object fields of serializable classes.
|
||||||
|
# This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects.
|
||||||
|
-if @kotlinx.serialization.Serializable class **
|
||||||
|
-keepclassmembers class <1> {
|
||||||
|
static <1>$Companion Companion;
|
||||||
|
}
|
||||||
|
|
||||||
|
-keepattributes RuntimeVisibleAnnotations,AnnotationDefault
|
||||||
|
-dontobfuscate
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,11 @@
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
||||||
|
tools:ignore="ScopedStorage" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
|
|
@ -212,16 +213,6 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name=".ui.ErrorDialogActivity"
|
|
||||||
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
|
|
||||||
android:excludeFromRecents="true"
|
|
||||||
android:exported="true"
|
|
||||||
android:launchMode="singleInstance"
|
|
||||||
android:theme="@style/Theme.BottomSheet">
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
||||||
android:enabled="false"
|
android:enabled="false"
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
|
||||||
import com.deniscerri.ytdlnis.ui.more.settings.SettingsActivity
|
import com.deniscerri.ytdlnis.ui.more.settings.SettingsActivity
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import com.deniscerri.ytdlnis.util.ThemeUtil
|
import com.deniscerri.ytdlnis.util.ThemeUtil
|
||||||
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.deniscerri.ytdlnis.util.UpdateUtil
|
import com.deniscerri.ytdlnis.util.UpdateUtil
|
||||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,10 @@ abstract class DBManager : RoomDatabase(){
|
||||||
abstract val logDao: LogDao
|
abstract val logDao: LogDao
|
||||||
abstract val terminalDao: TerminalDao
|
abstract val terminalDao: TerminalDao
|
||||||
|
|
||||||
|
enum class SORTING{
|
||||||
|
DESC, ASC
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
//prevents multiple instances of db getting created at the same time
|
//prevents multiple instances of db getting created at the same time
|
||||||
@Volatile
|
@Volatile
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,31 @@ package com.deniscerri.ytdlnis.database.dao
|
||||||
|
|
||||||
import androidx.room.*
|
import androidx.room.*
|
||||||
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
||||||
|
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||||
import com.deniscerri.ytdlnis.database.models.TemplateShortcut
|
import com.deniscerri.ytdlnis.database.models.TemplateShortcut
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
interface CommandTemplateDao {
|
interface CommandTemplateDao {
|
||||||
|
@Query("SELECT * FROM commandTemplates WHERE (title LIKE '%'||:query||'%' OR content LIKE '%'||:query||'%') ORDER BY " +
|
||||||
|
"CASE WHEN :sort = 'ASC' THEN id END ASC," +
|
||||||
|
"CASE WHEN :sort = 'DESC' THEN id END DESC," +
|
||||||
|
"CASE WHEN :sort = '' THEN id END DESC ")
|
||||||
|
fun getCommandsSortedByID(query : String, sort : String) : List<CommandTemplate>
|
||||||
|
|
||||||
|
|
||||||
|
@Query("SELECT * FROM commandTemplates WHERE (title LIKE '%'||:query||'%' OR content LIKE '%'||:query||'%') ORDER BY " +
|
||||||
|
"CASE WHEN :sort = 'ASC' THEN title COLLATE NOCASE END ASC," +
|
||||||
|
"CASE WHEN :sort = 'DESC' THEN title COLLATE NOCASE END DESC," +
|
||||||
|
"CASE WHEN :sort = '' THEN title COLLATE NOCASE END DESC ")
|
||||||
|
fun getCommandsSortedByTitle(query : String, sort : String) : List<CommandTemplate>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM commandTemplates WHERE (title LIKE '%'||:query||'%' OR content LIKE '%'||:query||'%') ORDER BY " +
|
||||||
|
"CASE WHEN :sort = 'ASC' THEN length(content) END ASC," +
|
||||||
|
"CASE WHEN :sort = 'DESC' THEN length(content) END DESC," +
|
||||||
|
"CASE WHEN :sort = '' THEN length(content) END DESC ")
|
||||||
|
fun getCommandsSortedByContentLength(query : String, sort : String) : List<CommandTemplate>
|
||||||
|
|
||||||
@Query("SELECT * FROM commandTemplates ORDER BY id DESC")
|
@Query("SELECT * FROM commandTemplates ORDER BY id DESC")
|
||||||
fun getAllTemplates() : List<CommandTemplate>
|
fun getAllTemplates() : List<CommandTemplate>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ interface HistoryDao {
|
||||||
fun getAllHistoryList() : List<HistoryItem>
|
fun getAllHistoryList() : List<HistoryItem>
|
||||||
|
|
||||||
@Query("SELECT * FROM history WHERE id=:id LIMIT 1")
|
@Query("SELECT * FROM history WHERE id=:id LIMIT 1")
|
||||||
suspend fun getHistoryItem(id: Int) : HistoryItem
|
fun getHistoryItem(id: Long) : HistoryItem
|
||||||
|
|
||||||
@Query("SELECT * FROM history WHERE url=:url")
|
@Query("SELECT * FROM history WHERE url=:url")
|
||||||
fun getAllHistoryByURL(url: String) : List<HistoryItem>
|
fun getAllHistoryByURL(url: String) : List<HistoryItem>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package com.deniscerri.ytdlnis.database.repository
|
package com.deniscerri.ytdlnis.database.repository
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import com.deniscerri.ytdlnis.database.DBManager
|
||||||
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
|
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
|
||||||
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
||||||
|
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||||
import com.deniscerri.ytdlnis.database.models.TemplateShortcut
|
import com.deniscerri.ytdlnis.database.models.TemplateShortcut
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
|
@ -10,10 +12,22 @@ class CommandTemplateRepository(private val commandDao: CommandTemplateDao) {
|
||||||
val items : Flow<List<CommandTemplate>> = commandDao.getAllTemplatesFlow()
|
val items : Flow<List<CommandTemplate>> = commandDao.getAllTemplatesFlow()
|
||||||
val shortcuts : Flow<List<TemplateShortcut>> = commandDao.getAllShortcutsFlow()
|
val shortcuts : Flow<List<TemplateShortcut>> = commandDao.getAllShortcutsFlow()
|
||||||
|
|
||||||
|
enum class CommandTemplateSortType {
|
||||||
|
DATE, TITLE, LENGTH
|
||||||
|
}
|
||||||
|
|
||||||
fun getAll() : List<CommandTemplate> {
|
fun getAll() : List<CommandTemplate> {
|
||||||
return commandDao.getAllTemplates()
|
return commandDao.getAllTemplates()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getFiltered(query : String, sortType: CommandTemplateSortType, sort: DBManager.SORTING) : List<CommandTemplate> {
|
||||||
|
return when(sortType){
|
||||||
|
CommandTemplateSortType.DATE -> commandDao.getCommandsSortedByID(query, sort.toString())
|
||||||
|
CommandTemplateSortType.TITLE -> commandDao.getCommandsSortedByTitle(query, sort.toString())
|
||||||
|
CommandTemplateSortType.LENGTH -> commandDao.getCommandsSortedByContentLength(query, sort.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getTotalNumber() : Int {
|
fun getTotalNumber() : Int {
|
||||||
return commandDao.getTotalNumber()
|
return commandDao.getTotalNumber()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,20 @@
|
||||||
package com.deniscerri.ytdlnis.database.repository
|
package com.deniscerri.ytdlnis.database.repository
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import com.deniscerri.ytdlnis.database.dao.HistoryDao
|
import com.deniscerri.ytdlnis.database.dao.HistoryDao
|
||||||
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import com.deniscerri.ytdlnis.database.DBManager.SORTING
|
||||||
|
|
||||||
class HistoryRepository(private val historyDao: HistoryDao) {
|
class HistoryRepository(private val historyDao: HistoryDao) {
|
||||||
val items : Flow<List<HistoryItem>> = historyDao.getAllHistory()
|
val items : Flow<List<HistoryItem>> = historyDao.getAllHistory()
|
||||||
enum class HistorySort{
|
|
||||||
DESC, ASC
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class HistorySortType {
|
enum class HistorySortType {
|
||||||
DATE, TITLE, AUTHOR, FILESIZE
|
DATE, TITLE, AUTHOR, FILESIZE
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getItem(id: Int) : HistoryItem {
|
fun getItem(id: Long) : HistoryItem {
|
||||||
return historyDao.getHistoryItem(id)
|
return historyDao.getHistoryItem(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -25,7 +22,11 @@ class HistoryRepository(private val historyDao: HistoryDao) {
|
||||||
return historyDao.getAllHistoryList()
|
return historyDao.getAllHistoryList()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFiltered(query : String, format : String, site : String, sortType: HistorySortType, sort: HistorySort) : List<HistoryItem> {
|
fun getAllByURL(url: String) : List<HistoryItem> {
|
||||||
|
return historyDao.getAllHistoryByURL(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFiltered(query : String, format : String, site : String, sortType: HistorySortType, sort: SORTING) : List<HistoryItem> {
|
||||||
return when(sortType){
|
return when(sortType){
|
||||||
HistorySortType.DATE -> historyDao.getHistorySortedByID(query, format, site, sort.toString())
|
HistorySortType.DATE -> historyDao.getHistorySortedByID(query, format, site, sort.toString())
|
||||||
HistorySortType.TITLE -> historyDao.getHistorySortedByTitle(query, format, site, sort.toString())
|
HistorySortType.TITLE -> historyDao.getHistorySortedByTitle(query, format, site, sort.toString())
|
||||||
|
|
@ -33,8 +34,8 @@ class HistoryRepository(private val historyDao: HistoryDao) {
|
||||||
HistorySortType.FILESIZE -> {
|
HistorySortType.FILESIZE -> {
|
||||||
val items = historyDao.getHistorySortedByID(query, format, site, sort.toString())
|
val items = historyDao.getHistorySortedByID(query, format, site, sort.toString())
|
||||||
when(sort){
|
when(sort){
|
||||||
HistorySort.DESC -> items.sortedByDescending { it.format.filesize }
|
SORTING.DESC -> items.sortedByDescending { it.format.filesize }
|
||||||
HistorySort.ASC -> items.sortedBy { it.format.filesize }
|
SORTING.ASC -> items.sortedBy { it.format.filesize }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -44,8 +45,11 @@ class HistoryRepository(private val historyDao: HistoryDao) {
|
||||||
historyDao.insert(item)
|
historyDao.insert(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun delete(item: HistoryItem){
|
suspend fun delete(item: HistoryItem, deleteFile: Boolean){
|
||||||
historyDao.delete(item.id)
|
historyDao.delete(item.id)
|
||||||
|
if (deleteFile){
|
||||||
|
FileUtil.deleteFile(item.downloadPath)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deleteAll(){
|
suspend fun deleteAll(){
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getOne(inputQuery: String, resetResults: Boolean) : ArrayList<ResultItem?>{
|
suspend fun getYoutubeVideo(inputQuery: String, resetResults: Boolean) : ArrayList<ResultItem?>{
|
||||||
val infoUtil = InfoUtil(context)
|
val infoUtil = InfoUtil(context)
|
||||||
val v = infoUtil.getYoutubeVideo(inputQuery)
|
val v = infoUtil.getYoutubeVideo(inputQuery)
|
||||||
if (resetResults) {
|
if (resetResults) {
|
||||||
|
|
@ -81,22 +81,17 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
|
||||||
|
|
||||||
suspend fun getDefault(inputQuery: String, resetResults: Boolean) : ArrayList<ResultItem?> {
|
suspend fun getDefault(inputQuery: String, resetResults: Boolean) : ArrayList<ResultItem?> {
|
||||||
val infoUtil = InfoUtil(context)
|
val infoUtil = InfoUtil(context)
|
||||||
try {
|
val items = infoUtil.getFromYTDL(inputQuery)
|
||||||
val items = infoUtil.getFromYTDL(inputQuery)
|
if (resetResults) {
|
||||||
if (resetResults) {
|
deleteAll()
|
||||||
deleteAll()
|
itemCount.value = items.size
|
||||||
itemCount.value = items.size
|
}else{
|
||||||
}else{
|
items.forEach { it!!.playlistTitle = "ytdlnis-Search" }
|
||||||
items.forEach { it!!.playlistTitle = "ytdlnis-Search" }
|
|
||||||
}
|
|
||||||
items.forEach {
|
|
||||||
resultDao.insert(it!!)
|
|
||||||
}
|
|
||||||
return items
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(tag, e.toString())
|
|
||||||
}
|
}
|
||||||
return arrayListOf()
|
items.forEach {
|
||||||
|
resultDao.insert(it!!)
|
||||||
|
}
|
||||||
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun delete(item: ResultItem){
|
suspend fun delete(item: ResultItem){
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,17 @@ import android.content.ClipboardManager
|
||||||
import android.content.Context.CLIPBOARD_SERVICE
|
import android.content.Context.CLIPBOARD_SERVICE
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MediatorLiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.asLiveData
|
import androidx.lifecycle.asLiveData
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.deniscerri.ytdlnis.database.DBManager
|
import com.deniscerri.ytdlnis.database.DBManager
|
||||||
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
||||||
import com.deniscerri.ytdlnis.database.models.CommandTemplateExport
|
import com.deniscerri.ytdlnis.database.models.CommandTemplateExport
|
||||||
|
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||||
import com.deniscerri.ytdlnis.database.models.TemplateShortcut
|
import com.deniscerri.ytdlnis.database.models.TemplateShortcut
|
||||||
import com.deniscerri.ytdlnis.database.repository.CommandTemplateRepository
|
import com.deniscerri.ytdlnis.database.repository.CommandTemplateRepository
|
||||||
|
import com.deniscerri.ytdlnis.database.repository.HistoryRepository
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
@ -21,15 +25,55 @@ import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
class CommandTemplateViewModel(private val application: Application) : AndroidViewModel(application) {
|
class CommandTemplateViewModel(private val application: Application) : AndroidViewModel(application) {
|
||||||
private val repository: CommandTemplateRepository
|
private val repository: CommandTemplateRepository
|
||||||
val items: LiveData<List<CommandTemplate>>
|
val sortOrder = MutableLiveData(DBManager.SORTING.DESC)
|
||||||
|
val sortType = MutableLiveData(CommandTemplateRepository.CommandTemplateSortType.DATE)
|
||||||
|
private val queryFilter = MutableLiveData("")
|
||||||
|
private var _items = MediatorLiveData<List<CommandTemplate>>()
|
||||||
|
val allItems: LiveData<List<CommandTemplate>>
|
||||||
|
|
||||||
val shortcuts : LiveData<List<TemplateShortcut>>
|
val shortcuts : LiveData<List<TemplateShortcut>>
|
||||||
private val jsonFormat = Json { prettyPrint = true }
|
private val jsonFormat = Json { prettyPrint = true }
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val dao = DBManager.getInstance(application).commandTemplateDao
|
val dao = DBManager.getInstance(application).commandTemplateDao
|
||||||
repository = CommandTemplateRepository(dao)
|
repository = CommandTemplateRepository(dao)
|
||||||
items = repository.items.asLiveData()
|
allItems = repository.items.asLiveData()
|
||||||
shortcuts = repository.shortcuts.asLiveData()
|
shortcuts = repository.shortcuts.asLiveData()
|
||||||
|
|
||||||
|
_items.addSource(allItems){
|
||||||
|
filter(queryFilter.value!!, sortType.value!!, sortOrder.value!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
_items.addSource(sortType){
|
||||||
|
filter(queryFilter.value!!, sortType.value!!, sortOrder.value!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
_items.addSource(queryFilter){
|
||||||
|
filter(queryFilter.value!!, sortType.value!!, sortOrder.value!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFilteredList() : LiveData<List<CommandTemplate>>{
|
||||||
|
return _items
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSorting(sort: CommandTemplateRepository.CommandTemplateSortType){
|
||||||
|
if (sortType.value != sort){
|
||||||
|
sortOrder.value = DBManager.SORTING.DESC
|
||||||
|
}else{
|
||||||
|
sortOrder.value = if (sortOrder.value == DBManager.SORTING.DESC) {
|
||||||
|
DBManager.SORTING.ASC
|
||||||
|
} else DBManager.SORTING.DESC
|
||||||
|
}
|
||||||
|
sortType.value = sort
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun filter(query : String, sortType: CommandTemplateRepository.CommandTemplateSortType, sort: DBManager.SORTING) = viewModelScope.launch(Dispatchers.IO){
|
||||||
|
_items.postValue(repository.getFiltered(query, sortType, sort))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setQueryFilter(filter: String){
|
||||||
|
queryFilter.value = filter
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getTemplate(itemId: Long): CommandTemplate {
|
fun getTemplate(itemId: Long): CommandTemplate {
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,17 @@ package com.deniscerri.ytdlnis.database.viewmodel
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
|
import android.os.Bundle
|
||||||
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.MutableLiveData
|
||||||
import androidx.lifecycle.asLiveData
|
import androidx.lifecycle.asLiveData
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.paging.PagingData
|
import androidx.paging.PagingData
|
||||||
|
|
@ -40,7 +40,7 @@ import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||||
import com.deniscerri.ytdlnis.database.models.VideoPreferences
|
import com.deniscerri.ytdlnis.database.models.VideoPreferences
|
||||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
import com.deniscerri.ytdlnis.ui.ErrorDialogActivity
|
import com.deniscerri.ytdlnis.database.repository.HistoryRepository
|
||||||
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.work.AlarmScheduler
|
import com.deniscerri.ytdlnis.work.AlarmScheduler
|
||||||
|
|
@ -49,8 +49,11 @@ import com.google.gson.Gson
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
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.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
@ -85,7 +88,22 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
private var videoCodec: String?
|
private var videoCodec: String?
|
||||||
private var audioCodec: String?
|
private var audioCodec: String?
|
||||||
private val dao: DownloadDao
|
private val dao: DownloadDao
|
||||||
private val historyDao: HistoryDao
|
private val historyRepository: HistoryRepository
|
||||||
|
|
||||||
|
data class DownloadsUiState(
|
||||||
|
var errorMessage: Pair<Int, Int>?,
|
||||||
|
//action title, type, data
|
||||||
|
var actions: MutableList<Triple<Int, DownloadsAction, List<DownloadItem>?>>?
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class DownloadsAction {
|
||||||
|
DOWNLOAD_ANYWAY
|
||||||
|
}
|
||||||
|
|
||||||
|
val uiState: MutableStateFlow<DownloadsUiState> = MutableStateFlow(DownloadsUiState(
|
||||||
|
errorMessage = null,
|
||||||
|
actions = null
|
||||||
|
))
|
||||||
|
|
||||||
enum class Type {
|
enum class Type {
|
||||||
auto, audio, video, command
|
auto, audio, video, command
|
||||||
|
|
@ -100,8 +118,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
init {
|
init {
|
||||||
dbManager = DBManager.getInstance(application)
|
dbManager = DBManager.getInstance(application)
|
||||||
dao = dbManager.downloadDao
|
dao = dbManager.downloadDao
|
||||||
historyDao = dbManager.historyDao
|
|
||||||
repository = DownloadRepository(dao)
|
repository = DownloadRepository(dao)
|
||||||
|
historyRepository = HistoryRepository(dbManager.historyDao)
|
||||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
|
||||||
commandTemplateDao = DBManager.getInstance(application).commandTemplateDao
|
commandTemplateDao = DBManager.getInstance(application).commandTemplateDao
|
||||||
infoUtil = InfoUtil(application)
|
infoUtil = InfoUtil(application)
|
||||||
|
|
@ -178,6 +196,10 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
return repository.getItemByID(id)
|
return repository.getItemByID(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getHistoryItemById(id: Long) : HistoryItem? {
|
||||||
|
return historyRepository.getItem(id)
|
||||||
|
}
|
||||||
|
|
||||||
fun getDownloadType(t: Type? = null, url: String) : Type {
|
fun getDownloadType(t: Type? = null, url: String) : Type {
|
||||||
var type = t
|
var type = t
|
||||||
|
|
||||||
|
|
@ -249,6 +271,10 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (preferredAudioFormats.isEmpty()){
|
||||||
|
preferredAudioFormats.add(getFormat(resultItem.formats, Type.audio).format_id)
|
||||||
|
}
|
||||||
|
|
||||||
val videoPreferences = VideoPreferences(
|
val videoPreferences = VideoPreferences(
|
||||||
embedSubs,
|
embedSubs,
|
||||||
addChapters, false,
|
addChapters, false,
|
||||||
|
|
@ -565,10 +591,12 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
startDownloadWorker(emptyList())
|
startDownloadWorker(emptyList())
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun queueDownloads(items: List<DownloadItem>) = CoroutineScope(Dispatchers.IO).launch {
|
suspend fun queueDownloads(items: List<DownloadItem>, ignoreDuplicate : Boolean = false) {
|
||||||
val context = App.instance
|
val context = App.instance
|
||||||
val alarmScheduler = AlarmScheduler(context)
|
val alarmScheduler = AlarmScheduler(context)
|
||||||
val activeAndQueuedDownloads = repository.getActiveAndQueuedDownloads()
|
val activeAndQueuedDownloads = withContext(Dispatchers.IO){
|
||||||
|
repository.getActiveAndQueuedDownloads()
|
||||||
|
}
|
||||||
val queuedItems = mutableListOf<DownloadItem>()
|
val queuedItems = mutableListOf<DownloadItem>()
|
||||||
val existing = mutableListOf<DownloadItem>()
|
val existing = mutableListOf<DownloadItem>()
|
||||||
|
|
||||||
|
|
@ -578,30 +606,43 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
alarmScheduler.schedule()
|
alarmScheduler.schedule()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
items.forEach {
|
items.forEach {
|
||||||
if (it.status != DownloadRepository.Status.Paused.toString()) it.status = DownloadRepository.Status.Queued.toString()
|
if (it.status != DownloadRepository.Status.Paused.toString()) it.status = DownloadRepository.Status.Queued.toString()
|
||||||
val currentCommand = infoUtil.buildYoutubeDLRequest(it)
|
val currentCommand = infoUtil.buildYoutubeDLRequest(it)
|
||||||
val parsedCurrentCommand = infoUtil.parseYTDLRequestString(currentCommand)
|
val parsedCurrentCommand = infoUtil.parseYTDLRequestString(currentCommand)
|
||||||
if (activeAndQueuedDownloads.firstOrNull{d ->
|
val existingDownload = activeAndQueuedDownloads.firstOrNull{d ->
|
||||||
d.id = 0
|
d.id = 0
|
||||||
d.logID = null
|
d.logID = null
|
||||||
d.customFileNameTemplate = it.customFileNameTemplate
|
d.customFileNameTemplate = it.customFileNameTemplate
|
||||||
d.status = DownloadRepository.Status.Queued.toString()
|
d.status = DownloadRepository.Status.Queued.toString()
|
||||||
d.toString() == it.toString()
|
d.toString() == it.toString()
|
||||||
} != null) {
|
}
|
||||||
|
if (existingDownload != null && !ignoreDuplicate) {
|
||||||
|
it.id = existingDownload.id
|
||||||
existing.add(it)
|
existing.add(it)
|
||||||
}else{
|
}else{
|
||||||
//check if downloaded and file exists
|
//check if downloaded and file exists
|
||||||
val history = historyDao.getAllHistoryByURL(it.url).filter { item -> FileUtil.exists(item.downloadPath) }
|
val history = withContext(Dispatchers.IO){
|
||||||
if (history.any { h -> h.command.replace("-P \"(.*?)\"".toRegex(), "") == parsedCurrentCommand.replace("-P \"(.*?)\"".toRegex(), "") }){
|
historyRepository.getAllByURL(it.url).filter { item -> FileUtil.exists(item.downloadPath) }
|
||||||
|
}
|
||||||
|
|
||||||
|
val existingHistory = history.firstOrNull {
|
||||||
|
h -> h.command.replace("(-P \"(.*?)\")|(--trim-filenames \"(.*?)\")".toRegex(), "") == parsedCurrentCommand.replace("(-P \"(.*?)\")|(--trim-filenames \"(.*?)\")".toRegex(), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingHistory != null && !ignoreDuplicate){
|
||||||
|
it.id = existingHistory.id
|
||||||
existing.add(it)
|
existing.add(it)
|
||||||
}else{
|
}else{
|
||||||
if (it.id == 0L){
|
if (it.id == 0L){
|
||||||
val id = runBlocking {repository.insert(it)}
|
val id = withContext(Dispatchers.IO){
|
||||||
|
repository.insert(it)
|
||||||
|
}
|
||||||
it.id = id
|
it.id = id
|
||||||
}else if (it.status == DownloadRepository.Status.Queued.toString()){
|
}else if (it.status == DownloadRepository.Status.Queued.toString()){
|
||||||
repository.update(it)
|
withContext(Dispatchers.IO){
|
||||||
|
repository.update(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
queuedItems.add(it)
|
queuedItems.add(it)
|
||||||
|
|
@ -610,13 +651,13 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existing.isNotEmpty()){
|
if (existing.isNotEmpty()){
|
||||||
val intent = Intent(context, ErrorDialogActivity::class.java)
|
uiState.update { u -> u.copy(
|
||||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
errorMessage = Pair(R.string.download_already_exists, R.string.download_already_exists_summary),
|
||||||
intent.putExtra("title", context.resources.getString(R.string.download_already_exists))
|
actions = mutableListOf(
|
||||||
intent.putExtra("message",
|
Triple(R.string.download, DownloadsAction.DOWNLOAD_ANYWAY, existing),
|
||||||
"${existing.size}/${items.size}\n" +
|
)
|
||||||
existing.mapIndexed { index, downloadItem -> "${index+1}. ${downloadItem.title}" }.joinToString("\n"))
|
)
|
||||||
context.startActivity(intent)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!useScheduler || alarmScheduler.isDuringTheScheduledTime() || items.any { it.downloadStartTime > 0L } ){
|
if (!useScheduler || alarmScheduler.isDuringTheScheduledTime() || items.any { it.downloadStartTime > 0L } ){
|
||||||
|
|
@ -664,10 +705,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
.setConstraints(workConstraints.build())
|
.setConstraints(workConstraints.build())
|
||||||
.setInitialDelay(delay, TimeUnit.MILLISECONDS)
|
.setInitialDelay(delay, TimeUnit.MILLISECONDS)
|
||||||
|
|
||||||
if (delay > 1000L){
|
queuedItems.forEach {
|
||||||
queuedItems.forEach {
|
workRequest.addTag(it.id.toString())
|
||||||
workRequest.addTag(it.id.toString())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
workManager.enqueueUniqueWork(
|
workManager.enqueueUniqueWork(
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import androidx.lifecycle.viewModelScope
|
||||||
import com.deniscerri.ytdlnis.database.DBManager
|
import com.deniscerri.ytdlnis.database.DBManager
|
||||||
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||||
import com.deniscerri.ytdlnis.database.repository.HistoryRepository
|
import com.deniscerri.ytdlnis.database.repository.HistoryRepository
|
||||||
import com.deniscerri.ytdlnis.database.repository.HistoryRepository.HistorySort
|
import com.deniscerri.ytdlnis.database.DBManager.SORTING
|
||||||
import com.deniscerri.ytdlnis.database.repository.HistoryRepository.HistorySortType
|
import com.deniscerri.ytdlnis.database.repository.HistoryRepository.HistorySortType
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|
@ -18,7 +18,7 @@ import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class HistoryViewModel(application: Application) : AndroidViewModel(application) {
|
class HistoryViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
private val repository : HistoryRepository
|
private val repository : HistoryRepository
|
||||||
val sortOrder = MutableLiveData(HistorySort.DESC)
|
val sortOrder = MutableLiveData(SORTING.DESC)
|
||||||
val sortType = MutableLiveData(HistorySortType.DATE)
|
val sortType = MutableLiveData(HistorySortType.DATE)
|
||||||
val websiteFilter = MutableLiveData("")
|
val websiteFilter = MutableLiveData("")
|
||||||
private val queryFilter = MutableLiveData("")
|
private val queryFilter = MutableLiveData("")
|
||||||
|
|
@ -55,11 +55,11 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application)
|
||||||
|
|
||||||
fun setSorting(sort: HistorySortType){
|
fun setSorting(sort: HistorySortType){
|
||||||
if (sortType.value != sort){
|
if (sortType.value != sort){
|
||||||
sortOrder.value = HistorySort.DESC
|
sortOrder.value = SORTING.DESC
|
||||||
}else{
|
}else{
|
||||||
sortOrder.value = if (sortOrder.value == HistorySort.DESC) {
|
sortOrder.value = if (sortOrder.value == SORTING.DESC) {
|
||||||
HistorySort.ASC
|
SORTING.ASC
|
||||||
} else HistorySort.DESC
|
} else SORTING.DESC
|
||||||
}
|
}
|
||||||
sortType.value = sort
|
sortType.value = sort
|
||||||
}
|
}
|
||||||
|
|
@ -76,7 +76,7 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application)
|
||||||
formatFilter.value = filter
|
formatFilter.value = filter
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun filter(query : String, format : String, site : String, sortType: HistorySortType, sort: HistorySort) = viewModelScope.launch(Dispatchers.IO){
|
private fun filter(query : String, format : String, site : String, sortType: HistorySortType, sort: SORTING) = viewModelScope.launch(Dispatchers.IO){
|
||||||
_items.postValue(repository.getFiltered(query, format, site, sortType, sort))
|
_items.postValue(repository.getFiltered(query, format, site, sortType, sort))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,10 +89,7 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun delete(item: HistoryItem, deleteFile: Boolean) = viewModelScope.launch(Dispatchers.IO){
|
fun delete(item: HistoryItem, deleteFile: Boolean) = viewModelScope.launch(Dispatchers.IO){
|
||||||
repository.delete(item)
|
repository.delete(item, deleteFile)
|
||||||
if (deleteFile){
|
|
||||||
FileUtil.deleteFile(item.downloadPath)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteAll() = viewModelScope.launch(Dispatchers.IO) {
|
fun deleteAll() = viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,11 @@ import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||||
import com.deniscerri.ytdlnis.database.models.SearchHistoryItem
|
import com.deniscerri.ytdlnis.database.models.SearchHistoryItem
|
||||||
import com.deniscerri.ytdlnis.database.repository.ResultRepository
|
import com.deniscerri.ytdlnis.database.repository.ResultRepository
|
||||||
import com.deniscerri.ytdlnis.database.repository.SearchHistoryRepository
|
import com.deniscerri.ytdlnis.database.repository.SearchHistoryRepository
|
||||||
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
|
|
@ -27,19 +31,28 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
|
||||||
val repository : ResultRepository
|
val repository : ResultRepository
|
||||||
private val searchHistoryRepository : SearchHistoryRepository
|
private val searchHistoryRepository : SearchHistoryRepository
|
||||||
val items : LiveData<List<ResultItem>>
|
val items : LiveData<List<ResultItem>>
|
||||||
val loadingItems = MutableLiveData<Boolean>()
|
data class ResultsUiState(
|
||||||
private val sharedPreferences: SharedPreferences
|
var processing: Boolean,
|
||||||
var state: ResultsState = ResultsState.IDLE
|
var errorMessage: Pair<Int, String>?,
|
||||||
enum class ResultsState {
|
var actions: MutableList<Pair<Int, ResultAction>>?
|
||||||
PROCESSING, IDLE
|
)
|
||||||
|
|
||||||
|
enum class ResultAction {
|
||||||
|
COPY_LOG
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val uiState: MutableStateFlow<ResultsUiState> = MutableStateFlow(ResultsUiState(
|
||||||
|
processing = false,
|
||||||
|
errorMessage = null,
|
||||||
|
actions = null
|
||||||
|
))
|
||||||
|
private val sharedPreferences: SharedPreferences
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val dao = DBManager.getInstance(application).resultDao
|
val dao = DBManager.getInstance(application).resultDao
|
||||||
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()
|
||||||
loadingItems.postValue(false)
|
|
||||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,54 +77,38 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
|
||||||
deleteAll()
|
deleteAll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
suspend fun parseQueries(inputQueries: List<String>) : MutableList<ResultItem?>{
|
suspend fun parseQueries(inputQueries: List<String>) : List<ResultItem?> {
|
||||||
state = ResultsState.PROCESSING
|
if (inputQueries.size > 1){
|
||||||
return if (inputQueries.size == 1){
|
|
||||||
parseQuery(inputQueries[0], true)
|
|
||||||
}else {
|
|
||||||
repository.itemCount.value = inputQueries.size
|
repository.itemCount.value = inputQueries.size
|
||||||
loadingItems.postValue(true)
|
|
||||||
val items = mutableListOf<ResultItem?>()
|
|
||||||
inputQueries.forEach {
|
|
||||||
items.addAll(parseQuery(it, false))
|
|
||||||
}
|
|
||||||
state = ResultsState.IDLE
|
|
||||||
loadingItems.postValue(false)
|
|
||||||
items
|
|
||||||
}
|
}
|
||||||
}
|
val resetResults = inputQueries.size == 1
|
||||||
|
|
||||||
suspend fun parseQuery(inputQuery: String, resetResults: Boolean) : ArrayList<ResultItem?> {
|
uiState.update {it.copy(processing = true, errorMessage = null, actions = null)}
|
||||||
if (resetResults)
|
|
||||||
loadingItems.postValue(true)
|
|
||||||
val type = getQueryType(inputQuery)
|
|
||||||
var res = arrayListOf<ResultItem?>()
|
|
||||||
return withContext(Dispatchers.IO){
|
return withContext(Dispatchers.IO){
|
||||||
try {
|
val res = mutableListOf<ResultItem?>()
|
||||||
when (type) {
|
inputQueries.forEach { inputQuery ->
|
||||||
"Search" -> {
|
val type = getQueryType(inputQuery)
|
||||||
res = repository.search(inputQuery, resetResults)
|
try {
|
||||||
}
|
when (type) {
|
||||||
"YT_Video" -> {
|
"Search" -> res.addAll(repository.search(inputQuery, resetResults))
|
||||||
res = repository.getOne(inputQuery, resetResults)
|
"YT_Video" -> res.addAll(repository.getYoutubeVideo(inputQuery, resetResults))
|
||||||
}
|
"YT_Playlist" -> res.addAll(repository.getPlaylist(inputQuery, resetResults))
|
||||||
"YT_Playlist" -> {
|
else -> res.addAll(repository.getDefault(inputQuery, resetResults))
|
||||||
res = repository.getPlaylist(inputQuery, resetResults)
|
|
||||||
}
|
|
||||||
"Default" -> {
|
|
||||||
res = repository.getDefault(inputQuery, resetResults)
|
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
uiState.update {it.copy(
|
||||||
|
processing = false,
|
||||||
|
errorMessage = Pair(R.string.no_results, e.message.toString()),
|
||||||
|
actions = mutableListOf(Pair(R.string.copy_log, ResultAction.COPY_LOG))
|
||||||
|
)}
|
||||||
|
Log.e(tag, e.toString())
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(tag, e.toString())
|
|
||||||
}
|
|
||||||
if (resetResults) {
|
|
||||||
state = ResultsState.IDLE
|
|
||||||
loadingItems.postValue(false)
|
|
||||||
}
|
}
|
||||||
|
uiState.update {it.copy(processing = false)}
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getQueryType(inputQuery: String) : String {
|
private fun getQueryType(inputQuery: String) : String {
|
||||||
var type = "Search"
|
var type = "Search"
|
||||||
val p = Pattern.compile("(^(https?)://(www.)?youtu(.be)?)|(^(https?)://(www.)?piped.video)")
|
val p = Pattern.compile("(^(https?)://(www.)?youtu(.be)?)|(^(https?)://(www.)?piped.video)")
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,7 @@ import android.os.Bundle
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.Patterns
|
import android.util.Patterns
|
||||||
import android.view.Window
|
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.widget.TextView
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
|
|
@ -35,7 +32,6 @@ 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.SelectPlaylistItemsDialog
|
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.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
package com.deniscerri.ytdlnis.ui
|
|
||||||
|
|
||||||
import android.content.ClipboardManager
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.graphics.drawable.ColorDrawable
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.WindowManager
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.core.view.ViewCompat
|
|
||||||
import androidx.core.view.WindowCompat
|
|
||||||
import com.deniscerri.ytdlnis.R
|
|
||||||
import com.deniscerri.ytdlnis.util.ThemeUtil
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import com.google.android.material.elevation.SurfaceColors
|
|
||||||
|
|
||||||
open class ErrorDialogActivity : BaseActivity() {
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
val title = intent.extras?.getString("title") ?: ""
|
|
||||||
val message = intent.extras?.getString("message") ?: ""
|
|
||||||
|
|
||||||
ThemeUtil.updateTheme(this)
|
|
||||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { v, insets ->
|
|
||||||
v.setPadding(0, 0, 0, 0)
|
|
||||||
insets
|
|
||||||
}
|
|
||||||
window.run {
|
|
||||||
setBackgroundDrawable(ColorDrawable(0))
|
|
||||||
setLayout(
|
|
||||||
WindowManager.LayoutParams.MATCH_PARENT,
|
|
||||||
WindowManager.LayoutParams.MATCH_PARENT
|
|
||||||
)
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY)
|
|
||||||
} else {
|
|
||||||
setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val errDialog = MaterialAlertDialogBuilder(this)
|
|
||||||
.setTitle(title)
|
|
||||||
.setMessage(message)
|
|
||||||
.setIcon(R.drawable.ic_cancel)
|
|
||||||
.setPositiveButton(getString(R.string.copy_log)) { _: DialogInterface?, _: Int ->
|
|
||||||
val clipboard: ClipboardManager =
|
|
||||||
getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
|
||||||
clipboard.setText(message)
|
|
||||||
this.finish()
|
|
||||||
}.setOnDismissListener {
|
|
||||||
this.finish()
|
|
||||||
}
|
|
||||||
this.runOnUiThread {
|
|
||||||
errDialog.show()
|
|
||||||
}
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -40,6 +40,7 @@ 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
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
|
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
|
||||||
import com.facebook.shimmer.ShimmerFrameLayout
|
import com.facebook.shimmer.ShimmerFrameLayout
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
|
|
@ -54,6 +55,8 @@ import com.google.android.material.search.SearchBar
|
||||||
import com.google.android.material.search.SearchView
|
import com.google.android.material.search.SearchView
|
||||||
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.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
@ -105,6 +108,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
|
||||||
activity = getActivity()
|
activity = getActivity()
|
||||||
mainActivity = activity as MainActivity?
|
mainActivity = activity as MainActivity?
|
||||||
quickLaunchSheet = false
|
quickLaunchSheet = false
|
||||||
|
infoUtil = InfoUtil(requireContext())
|
||||||
return fragmentView
|
return fragmentView
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,6 +140,9 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
|
||||||
searchSuggestionsLinearLayout = view.findViewById(R.id.search_suggestions_linear_layout)
|
searchSuggestionsLinearLayout = view.findViewById(R.id.search_suggestions_linear_layout)
|
||||||
searchHistory = view.findViewById(R.id.search_history_scroll_view)
|
searchHistory = view.findViewById(R.id.search_history_scroll_view)
|
||||||
searchHistoryLinearLayout = view.findViewById(R.id.search_history_linear_layout)
|
searchHistoryLinearLayout = view.findViewById(R.id.search_history_linear_layout)
|
||||||
|
homeFabs = view.findViewById(R.id.home_fabs)
|
||||||
|
downloadFabs = homeFabs!!.findViewById(R.id.download_selected_coordinator)
|
||||||
|
downloadAllFabCoordinator = homeFabs!!.findViewById(R.id.download_all_coordinator)
|
||||||
|
|
||||||
runCatching { materialToolbar!!.title = ThemeUtil.getStyledAppName(requireContext()) }
|
runCatching { materialToolbar!!.title = ThemeUtil.getStyledAppName(requireContext()) }
|
||||||
|
|
||||||
|
|
@ -174,30 +181,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
|
||||||
quickLaunchSheet = true
|
quickLaunchSheet = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
resultViewModel.loadingItems.observe(viewLifecycleOwner){
|
|
||||||
loadingItems = it
|
|
||||||
if (it){
|
|
||||||
recyclerView?.setPadding(0,0,0,0)
|
|
||||||
shimmerCards!!.startShimmer()
|
|
||||||
shimmerCards!!.visibility = VISIBLE
|
|
||||||
}else{
|
|
||||||
recyclerView?.setPadding(0,0,0,100)
|
|
||||||
shimmerCards!!.stopShimmer()
|
|
||||||
shimmerCards!!.visibility = GONE
|
|
||||||
if (resultsList!!.size > 1 && resultsList!![0]!!.playlistTitle.isNotEmpty()){
|
|
||||||
downloadAllFabCoordinator!!.visibility = VISIBLE
|
|
||||||
}else{
|
|
||||||
downloadAllFabCoordinator!!.visibility = GONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
initMenu()
|
initMenu()
|
||||||
|
|
||||||
homeFabs = view.findViewById(R.id.home_fabs)
|
|
||||||
downloadFabs = homeFabs!!.findViewById(R.id.download_selected_coordinator)
|
|
||||||
downloadAllFabCoordinator = homeFabs!!.findViewById(R.id.download_all_coordinator)
|
|
||||||
val downloadSelectedFab = downloadFabs!!.findViewById<ExtendedFloatingActionButton>(R.id.download_selected_fab)
|
val downloadSelectedFab = downloadFabs!!.findViewById<ExtendedFloatingActionButton>(R.id.download_selected_fab)
|
||||||
downloadSelectedFab.tag = "downloadSelected"
|
downloadSelectedFab.tag = "downloadSelected"
|
||||||
downloadSelectedFab.setOnClickListener(this)
|
downloadSelectedFab.setOnClickListener(this)
|
||||||
|
|
@ -239,15 +223,40 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
launch{
|
||||||
|
resultViewModel.uiState.collectLatest { res ->
|
||||||
|
if (res.errorMessage != null){
|
||||||
|
kotlin.runCatching { UiUtil.handleResultResponse(requireActivity(), res) }
|
||||||
|
resultViewModel.uiState.update {it.copy(errorMessage = null, actions = null) }
|
||||||
|
}
|
||||||
|
|
||||||
|
loadingItems = res.processing
|
||||||
|
if (res.processing){
|
||||||
|
recyclerView?.setPadding(0,0,0,0)
|
||||||
|
shimmerCards!!.startShimmer()
|
||||||
|
shimmerCards!!.visibility = VISIBLE
|
||||||
|
}else{
|
||||||
|
recyclerView?.setPadding(0,0,0,100)
|
||||||
|
shimmerCards!!.stopShimmer()
|
||||||
|
shimmerCards!!.visibility = GONE
|
||||||
|
if (resultsList!!.size > 1 && resultsList!![0]!!.playlistTitle.isNotEmpty()){
|
||||||
|
downloadAllFabCoordinator!!.visibility = VISIBLE
|
||||||
|
}else{
|
||||||
|
downloadAllFabCoordinator!!.visibility = GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
if(arguments?.getString("url") == null){
|
if(arguments?.getString("url") == null){
|
||||||
if (resultViewModel.state == ResultViewModel.ResultsState.IDLE){
|
if (!resultViewModel.uiState.value.processing){
|
||||||
resultViewModel.checkTrending()
|
resultViewModel.checkTrending()
|
||||||
}else{
|
|
||||||
resultViewModel.loadingItems.postValue(true)
|
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
arguments?.remove("url")
|
arguments?.remove("url")
|
||||||
|
|
@ -302,7 +311,6 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
infoUtil = InfoUtil(requireContext())
|
|
||||||
searchView!!.addTransitionListener { _, _, newState ->
|
searchView!!.addTransitionListener { _, _, newState ->
|
||||||
if (newState == SearchView.TransitionState.SHOWN) {
|
if (newState == SearchView.TransitionState.SHOWN) {
|
||||||
val currentProvider = sharedPreferences?.getString("search_engine", "ytsearch")
|
val currentProvider = sharedPreferences?.getString("search_engine", "ytsearch")
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ 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
|
||||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil.setTextAndRecalculateWidth
|
import com.deniscerri.ytdlnis.util.UiUtil.setTextAndRecalculateWidth
|
||||||
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.BottomSheetBehavior
|
||||||
|
|
@ -496,10 +497,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
||||||
}
|
}
|
||||||
|
|
||||||
chip.setOnLongClickListener {
|
chip.setOnLongClickListener {
|
||||||
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
|
UiUtil.showGenericDeleteDialog(requireContext(), chip.text.toString(), accepted = {
|
||||||
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + chip.text + "\"!")
|
|
||||||
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
|
||||||
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
|
||||||
player.seekTo(0)
|
player.seekTo(0)
|
||||||
player.pause()
|
player.pause()
|
||||||
chipGroup.removeView(chip)
|
chipGroup.removeView(chip)
|
||||||
|
|
@ -509,8 +507,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
||||||
player.stop()
|
player.stop()
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
deleteDialog.show()
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.deniscerri.ytdlnis.ui.downloadcard
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.SharedPreferences
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
|
|
@ -11,11 +12,13 @@ import android.view.ViewGroup
|
||||||
import android.widget.AdapterView
|
import android.widget.AdapterView
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import android.widget.AutoCompleteTextView
|
import android.widget.AutoCompleteTextView
|
||||||
|
import android.widget.LinearLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
import androidx.core.view.isEmpty
|
import androidx.core.view.isEmpty
|
||||||
|
import androidx.core.view.isVisible
|
||||||
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
|
||||||
|
|
@ -52,6 +55,8 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
|
||||||
lateinit var downloadItem : DownloadItem
|
lateinit var downloadItem : DownloadItem
|
||||||
lateinit var title : TextInputLayout
|
lateinit var title : TextInputLayout
|
||||||
lateinit var author : TextInputLayout
|
lateinit var author : TextInputLayout
|
||||||
|
lateinit var preferences: SharedPreferences
|
||||||
|
lateinit var shownFields: List<String>
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
|
|
@ -63,6 +68,8 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
|
||||||
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
||||||
infoUtil = InfoUtil(requireContext())
|
infoUtil = InfoUtil(requireContext())
|
||||||
genericAudioFormats = infoUtil.getGenericAudioFormats(requireContext().resources)
|
genericAudioFormats = infoUtil.getGenericAudioFormats(requireContext().resources)
|
||||||
|
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
|
shownFields = preferences.getStringSet("modify_download_card", setOf())!!.toList()
|
||||||
return fragmentView
|
return fragmentView
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,6 +89,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
|
||||||
|
|
||||||
try {
|
try {
|
||||||
title = view.findViewById(R.id.title_textinput)
|
title = view.findViewById(R.id.title_textinput)
|
||||||
|
title.visibility = if (shownFields.contains("title")) View.VISIBLE else View.GONE
|
||||||
if (title.editText?.text?.isEmpty() == true){
|
if (title.editText?.text?.isEmpty() == true){
|
||||||
title.editText!!.setText(downloadItem.title)
|
title.editText!!.setText(downloadItem.title)
|
||||||
}
|
}
|
||||||
|
|
@ -100,6 +108,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
|
||||||
}
|
}
|
||||||
|
|
||||||
author = view.findViewById(R.id.author_textinput)
|
author = view.findViewById(R.id.author_textinput)
|
||||||
|
author.visibility = if (shownFields.contains("author")) View.VISIBLE else View.GONE
|
||||||
if (author.editText?.text?.isEmpty() == true){
|
if (author.editText?.text?.isEmpty() == true){
|
||||||
author.editText!!.setText(downloadItem.author)
|
author.editText!!.setText(downloadItem.author)
|
||||||
}
|
}
|
||||||
|
|
@ -158,9 +167,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
|
||||||
if (currentDownloadItem!!.type != Type.audio){
|
if (currentDownloadItem!!.type != Type.audio){
|
||||||
downloadItem.type = Type.audio
|
downloadItem.type = Type.audio
|
||||||
runCatching {
|
runCatching {
|
||||||
downloadItem.format =
|
downloadItem.format = downloadViewModel.getFormat(downloadItem.allFormats, Type.audio)
|
||||||
downloadItem.allFormats.filter { it.format_note.contains("audio", ignoreCase = true) }
|
|
||||||
.maxByOrNull { it.filesize }!!
|
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
downloadItem.format = genericAudioFormats.last()
|
downloadItem.format = genericAudioFormats.last()
|
||||||
}
|
}
|
||||||
|
|
@ -172,6 +179,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
|
||||||
var containerPreference = sharedPreferences.getString("audio_format", "Default")
|
var containerPreference = sharedPreferences.getString("audio_format", "Default")
|
||||||
if (containerPreference == "Default") containerPreference = getString(R.string.defaultValue)
|
if (containerPreference == "Default") containerPreference = getString(R.string.defaultValue)
|
||||||
val container = view.findViewById<TextInputLayout>(R.id.downloadContainer)
|
val container = view.findViewById<TextInputLayout>(R.id.downloadContainer)
|
||||||
|
container.visibility = if (shownFields.contains("container")) View.VISIBLE else View.GONE
|
||||||
val containerAutoCompleteTextView =
|
val containerAutoCompleteTextView =
|
||||||
view.findViewById<AutoCompleteTextView>(R.id.container_textview)
|
view.findViewById<AutoCompleteTextView>(R.id.container_textview)
|
||||||
|
|
||||||
|
|
@ -190,6 +198,10 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
|
||||||
resultItem?.formats?.addAll(allFormats.first().filter { !genericAudioFormats.contains(it) })
|
resultItem?.formats?.addAll(allFormats.first().filter { !genericAudioFormats.contains(it) })
|
||||||
if (resultItem != null){
|
if (resultItem != null){
|
||||||
resultViewModel.update(resultItem!!)
|
resultViewModel.update(resultItem!!)
|
||||||
|
kotlin.runCatching {
|
||||||
|
val f1 = fragmentManager?.findFragmentByTag("f1") as DownloadVideoFragment
|
||||||
|
f1.updateUI(resultItem)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -229,44 +241,50 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
|
||||||
if (containers[index] == getString(R.string.defaultValue)) downloadItem.container = ""
|
if (containers[index] == getString(R.string.defaultValue)) downloadItem.container = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
UiUtil.configureAudio(
|
|
||||||
view,
|
|
||||||
requireActivity(),
|
|
||||||
listOf(downloadItem),
|
|
||||||
embedThumbClicked = {
|
|
||||||
downloadItem.audioPreferences.embedThumb = it
|
|
||||||
},
|
|
||||||
splitByChaptersClicked = {
|
|
||||||
downloadItem.audioPreferences.splitByChapters = it
|
|
||||||
},
|
|
||||||
filenameTemplateSet = {
|
|
||||||
downloadItem.customFileNameTemplate = it
|
|
||||||
},
|
|
||||||
sponsorBlockItemsSet = { values, checkedItems ->
|
|
||||||
downloadItem.audioPreferences.sponsorBlockFilters.clear()
|
|
||||||
for (i in checkedItems.indices) {
|
|
||||||
if (checkedItems[i]) {
|
|
||||||
downloadItem.audioPreferences.sponsorBlockFilters.add(values[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
cutClicked = {cutVideoListener ->
|
|
||||||
if (parentFragmentManager.findFragmentByTag("cutVideoSheet") == null){
|
|
||||||
val bottomSheet = CutVideoBottomSheetDialog(downloadItem, resultItem?.urls ?: "", resultItem?.chapters ?: listOf(), cutVideoListener)
|
|
||||||
bottomSheet.show(parentFragmentManager, "cutVideoSheet")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
extraCommandsClicked = {
|
|
||||||
val callback = object : ExtraCommandsListener {
|
|
||||||
override fun onChangeExtraCommand(c: String) {
|
|
||||||
downloadItem.extraCommands = c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val bottomSheetDialog = AddExtraCommandsDialog(downloadItem, callback)
|
view.findViewById<LinearLayout>(R.id.adjust).apply {
|
||||||
bottomSheetDialog.show(parentFragmentManager, "extraCommands")
|
visibility = if (shownFields.contains("adjust_audio")) View.VISIBLE else View.GONE
|
||||||
|
if (isVisible){
|
||||||
|
UiUtil.configureAudio(
|
||||||
|
view,
|
||||||
|
requireActivity(),
|
||||||
|
listOf(downloadItem),
|
||||||
|
embedThumbClicked = {
|
||||||
|
downloadItem.audioPreferences.embedThumb = it
|
||||||
|
},
|
||||||
|
splitByChaptersClicked = {
|
||||||
|
downloadItem.audioPreferences.splitByChapters = it
|
||||||
|
},
|
||||||
|
filenameTemplateSet = {
|
||||||
|
downloadItem.customFileNameTemplate = it
|
||||||
|
},
|
||||||
|
sponsorBlockItemsSet = { values, checkedItems ->
|
||||||
|
downloadItem.audioPreferences.sponsorBlockFilters.clear()
|
||||||
|
for (i in checkedItems.indices) {
|
||||||
|
if (checkedItems[i]) {
|
||||||
|
downloadItem.audioPreferences.sponsorBlockFilters.add(values[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cutClicked = {cutVideoListener ->
|
||||||
|
if (parentFragmentManager.findFragmentByTag("cutVideoSheet") == null){
|
||||||
|
val bottomSheet = CutVideoBottomSheetDialog(downloadItem, resultItem?.urls ?: "", resultItem?.chapters ?: listOf(), cutVideoListener)
|
||||||
|
bottomSheet.show(parentFragmentManager, "cutVideoSheet")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
extraCommandsClicked = {
|
||||||
|
val callback = object : ExtraCommandsListener {
|
||||||
|
override fun onChangeExtraCommand(c: String) {
|
||||||
|
downloadItem.extraCommands = c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val bottomSheetDialog = AddExtraCommandsDialog(downloadItem, callback)
|
||||||
|
bottomSheetDialog.show(parentFragmentManager, "extraCommands")
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
}catch (e : Exception){
|
}catch (e : Exception){
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,8 @@
|
||||||
package com.deniscerri.ytdlnis.ui.downloadcard
|
package com.deniscerri.ytdlnis.ui.downloadcard
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.ActivityManager
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.Context
|
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.Intent
|
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
|
@ -20,14 +17,12 @@ import android.widget.Button
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.fragment.app.DialogFragment
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.fragment.findNavController
|
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
|
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.dao.CommandTemplateDao
|
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
|
||||||
|
|
@ -45,9 +40,10 @@ 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.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
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.progressindicator.LinearProgressIndicator
|
||||||
import com.google.android.material.tabs.TabLayout
|
import com.google.android.material.tabs.TabLayout
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
@ -205,6 +201,11 @@ class DownloadBottomSheetDialog(private var result: ResultItem, private val type
|
||||||
viewPager2.setPageTransformer(BackgroundToForegroundPageTransformer())
|
viewPager2.setPageTransformer(BackgroundToForegroundPageTransformer())
|
||||||
|
|
||||||
val scheduleBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_schedule_button)
|
val scheduleBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_schedule_button)
|
||||||
|
scheduleBtn.visibility = if(sharedPreferences.getStringSet("modify_download_card", setOf())!!.contains("schedule")){
|
||||||
|
View.VISIBLE
|
||||||
|
}else{
|
||||||
|
View.GONE
|
||||||
|
}
|
||||||
val download = view.findViewById<Button>(R.id.bottomsheet_download_button)
|
val download = view.findViewById<Button>(R.id.bottomsheet_download_button)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -249,6 +250,11 @@ class DownloadBottomSheetDialog(private var result: ResultItem, private val type
|
||||||
}
|
}
|
||||||
|
|
||||||
val link = view.findViewById<Button>(R.id.bottom_sheet_link)
|
val link = view.findViewById<Button>(R.id.bottom_sheet_link)
|
||||||
|
link.visibility = if(sharedPreferences.getStringSet("modify_download_card", setOf())!!.contains("url")){
|
||||||
|
View.VISIBLE
|
||||||
|
}else{
|
||||||
|
View.GONE
|
||||||
|
}
|
||||||
val updateItem = view.findViewById<Button>(R.id.update_item)
|
val updateItem = view.findViewById<Button>(R.id.update_item)
|
||||||
if (Patterns.WEB_URL.matcher(result.url).matches()){
|
if (Patterns.WEB_URL.matcher(result.url).matches()){
|
||||||
link.text = result.url
|
link.text = result.url
|
||||||
|
|
@ -282,6 +288,23 @@ class DownloadBottomSheetDialog(private var result: ResultItem, private val type
|
||||||
//update in the background if there is no data
|
//update in the background if there is no data
|
||||||
if(result.title.isEmpty() && currentDownloadItem == null && !sharedPreferences.getBoolean("quick_download", false)){
|
if(result.title.isEmpty() && currentDownloadItem == null && !sharedPreferences.getBoolean("quick_download", false)){
|
||||||
initUpdateData(view)
|
initUpdateData(view)
|
||||||
|
}else {
|
||||||
|
val usingGenericFormatsOrEmpty = result.formats.isEmpty() || result.formats.any { it.format_note.contains("ytdlnisgeneric") }
|
||||||
|
if (usingGenericFormatsOrEmpty && sharedPreferences.getBoolean("update_formats", false)){
|
||||||
|
initUpdateFormats(result.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
downloadViewModel.uiState.collectLatest { res ->
|
||||||
|
if (res.errorMessage != null) {
|
||||||
|
UiUtil.handleDownloadsResponse(requireActivity() as MainActivity, res, downloadViewModel)
|
||||||
|
downloadViewModel.uiState.value = DownloadViewModel.DownloadsUiState(
|
||||||
|
errorMessage = null,
|
||||||
|
actions = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private fun getDownloadItem(selectedTabPosition: Int = tabLayout.selectedTabPosition) : DownloadItem{
|
private fun getDownloadItem(selectedTabPosition: Int = tabLayout.selectedTabPosition) : DownloadItem{
|
||||||
|
|
@ -342,7 +365,7 @@ class DownloadBottomSheetDialog(private var result: ResultItem, private val type
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
val result = resultViewModel.parseQuery(result.url, true)
|
val result = resultViewModel.parseQueries(listOf(result.url))
|
||||||
if (result.isEmpty()){
|
if (result.isEmpty()){
|
||||||
withContext(Dispatchers.Main){
|
withContext(Dispatchers.Main){
|
||||||
Looper.prepare().run {
|
Looper.prepare().run {
|
||||||
|
|
@ -376,6 +399,12 @@ class DownloadBottomSheetDialog(private var result: ResultItem, private val type
|
||||||
shimmerLoadingSubtitle.stopShimmer()
|
shimmerLoadingSubtitle.stopShimmer()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val usingGenericFormatsOrEmpty = result[0]!!.formats.isEmpty() || result[0]!!.formats.any { it.format_note.contains("ytdlnisgeneric") }
|
||||||
|
|
||||||
|
if (usingGenericFormatsOrEmpty && sharedPreferences.getBoolean("update_formats", false)){
|
||||||
|
initUpdateFormats(result[0]!!.url)
|
||||||
|
}
|
||||||
|
|
||||||
}else{
|
}else{
|
||||||
//open multi download card instead
|
//open multi download card instead
|
||||||
if (activity is ShareActivity){
|
if (activity is ShareActivity){
|
||||||
|
|
@ -394,6 +423,48 @@ class DownloadBottomSheetDialog(private var result: ResultItem, private val type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun initUpdateFormats(url: String){
|
||||||
|
CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) {
|
||||||
|
withContext(Dispatchers.Main){
|
||||||
|
runCatching {
|
||||||
|
val f1 = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
|
||||||
|
f1.view?.findViewById<LinearProgressIndicator>(R.id.format_loading_progress)?.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
runCatching {
|
||||||
|
val f1 = fragmentManager?.findFragmentByTag("f1") as DownloadVideoFragment
|
||||||
|
f1.view?.findViewById<LinearProgressIndicator>(R.id.format_loading_progress)?.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val formats = infoUtil.getFormats(url).toMutableList()
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main){
|
||||||
|
runCatching {
|
||||||
|
val f1 = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
|
||||||
|
val resultItem = downloadViewModel.createResultItemFromDownload(f1.downloadItem)
|
||||||
|
resultItem.formats = formats
|
||||||
|
fragmentAdapter.setResultItem(resultItem)
|
||||||
|
f1.updateUI(resultItem)
|
||||||
|
f1.view?.findViewById<LinearProgressIndicator>(R.id.format_loading_progress)?.visibility = View.GONE
|
||||||
|
}
|
||||||
|
runCatching {
|
||||||
|
val f1 = fragmentManager?.findFragmentByTag("f1") as DownloadVideoFragment
|
||||||
|
val resultItem = downloadViewModel.createResultItemFromDownload(f1.downloadItem)
|
||||||
|
resultItem.formats = formats
|
||||||
|
fragmentAdapter.setResultItem(resultItem)
|
||||||
|
f1.updateUI(resultItem)
|
||||||
|
f1.view?.findViewById<LinearProgressIndicator>(R.id.format_loading_progress)?.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formats.isNotEmpty()){
|
||||||
|
result.formats = formats
|
||||||
|
resultViewModel.update(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCancel(dialog: DialogInterface) {
|
override fun onCancel(dialog: DialogInterface) {
|
||||||
super.onCancel(dialog)
|
super.onCancel(dialog)
|
||||||
cleanUp()
|
cleanUp()
|
||||||
|
|
@ -407,7 +478,7 @@ class DownloadBottomSheetDialog(private var result: ResultItem, private val type
|
||||||
|
|
||||||
private fun cleanUp(){
|
private fun cleanUp(){
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
repeat(parentFragmentManager.findFragmentByTag("downloadSingleSheet")?.fragmentManager?.backStackEntryCount ?: 0){
|
repeat((parentFragmentManager.findFragmentByTag("downloadSingleSheet")?.fragmentManager?.backStackEntryCount?.minus(1))?: 0){
|
||||||
parentFragmentManager.findFragmentByTag("downloadSingleSheet")?.fragmentManager?.popBackStack()
|
parentFragmentManager.findFragmentByTag("downloadSingleSheet")?.fragmentManager?.popBackStack()
|
||||||
}
|
}
|
||||||
parentFragmentManager.beginTransaction().remove(parentFragmentManager.findFragmentByTag("downloadSingleSheet")!!).commit()
|
parentFragmentManager.beginTransaction().remove(parentFragmentManager.findFragmentByTag("downloadSingleSheet")!!).commit()
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.SharedPreferences
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
|
|
@ -17,9 +18,11 @@ import android.widget.*
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
|
import androidx.core.view.isVisible
|
||||||
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.preference.PreferenceManager
|
||||||
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.DownloadItem
|
||||||
|
|
@ -48,6 +51,8 @@ class DownloadCommandFragment(private val resultItem: ResultItem? = null, privat
|
||||||
private lateinit var commandTemplateViewModel : CommandTemplateViewModel
|
private lateinit var commandTemplateViewModel : CommandTemplateViewModel
|
||||||
private lateinit var saveDir : TextInputLayout
|
private lateinit var saveDir : TextInputLayout
|
||||||
private lateinit var freeSpace : TextView
|
private lateinit var freeSpace : TextView
|
||||||
|
private lateinit var preferences: SharedPreferences
|
||||||
|
private lateinit var shownFields: List<String>
|
||||||
|
|
||||||
lateinit var downloadItem: DownloadItem
|
lateinit var downloadItem: DownloadItem
|
||||||
|
|
||||||
|
|
@ -60,6 +65,8 @@ class DownloadCommandFragment(private val resultItem: ResultItem? = null, privat
|
||||||
activity = getActivity()
|
activity = getActivity()
|
||||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||||
commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java]
|
commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java]
|
||||||
|
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
|
shownFields = preferences.getStringSet("modify_download_card", setOf())!!.toList()
|
||||||
return fragmentView
|
return fragmentView
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -180,72 +187,76 @@ class DownloadCommandFragment(private val resultItem: ResultItem? = null, privat
|
||||||
commandTemplateViewModel.getTotalShortcutNumber()
|
commandTemplateViewModel.getTotalShortcutNumber()
|
||||||
}
|
}
|
||||||
|
|
||||||
UiUtil.configureCommand(
|
|
||||||
view,
|
view.findViewById<LinearLayout>(R.id.adjust).apply {
|
||||||
if (downloadItem.url.isEmpty()) 0 else 1,
|
visibility = if (shownFields.contains("adjust_command")) View.VISIBLE else View.GONE
|
||||||
shortcutCount,
|
if (isVisible){
|
||||||
newTemplateClicked = {
|
UiUtil.configureCommand(
|
||||||
UiUtil.showCommandTemplateCreationOrUpdatingSheet(null, requireActivity(), viewLifecycleOwner, commandTemplateViewModel) {
|
view,
|
||||||
templates.add(it)
|
if (downloadItem.url.isEmpty()) 0 else 1,
|
||||||
chosenCommandView.editText!!.setText(it.content)
|
shortcutCount,
|
||||||
downloadItem.format = Format(
|
newTemplateClicked = {
|
||||||
it.title,
|
UiUtil.showCommandTemplateCreationOrUpdatingSheet(null, requireActivity(), viewLifecycleOwner, commandTemplateViewModel) {
|
||||||
"",
|
templates.add(it)
|
||||||
"",
|
chosenCommandView.editText!!.setText(it.content)
|
||||||
"",
|
downloadItem.format = Format(
|
||||||
"",
|
it.title,
|
||||||
0,
|
"",
|
||||||
it.content
|
"",
|
||||||
)
|
"",
|
||||||
commandTemplateCard.visibility = View.VISIBLE
|
"",
|
||||||
view.findViewById<TextView>(R.id.command_txt).visibility = View.VISIBLE
|
0,
|
||||||
view.findViewById<Chip>(R.id.editSelected).isEnabled = true
|
it.content
|
||||||
populateCommandCard(commandTemplateCard, it)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
editSelectedClicked =
|
|
||||||
{
|
|
||||||
var current = templates.find { it.id == commandTemplateCard.tag }
|
|
||||||
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)
|
|
||||||
populateCommandCard(commandTemplateCard, it)
|
|
||||||
downloadItem.format = Format(
|
|
||||||
it.title,
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
0,
|
|
||||||
it.content
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
shortcutClicked = {
|
|
||||||
UiUtil.showShortcuts(requireActivity(), commandTemplateViewModel,
|
|
||||||
itemSelected = {
|
|
||||||
chosenCommandView.editText!!.setText("${chosenCommandView.editText!!.text} $it")
|
|
||||||
downloadItem.format.format_note = chosenCommandView.editText!!.text.toString()
|
|
||||||
},
|
|
||||||
itemRemoved = {removed ->
|
|
||||||
chosenCommandView.editText!!.setText(chosenCommandView.editText!!.text.replace("(${
|
|
||||||
Regex.escape(
|
|
||||||
removed
|
|
||||||
)
|
)
|
||||||
})(?!.*\\1)".toRegex(), "").trimEnd())
|
commandTemplateCard.visibility = View.VISIBLE
|
||||||
downloadItem.format.format_note = chosenCommandView.editText!!.text.toString().trimEnd()
|
view.findViewById<TextView>(R.id.command_txt).visibility = View.VISIBLE
|
||||||
})
|
view.findViewById<Chip>(R.id.editSelected).isEnabled = true
|
||||||
|
populateCommandCard(commandTemplateCard, it)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
editSelectedClicked =
|
||||||
|
{
|
||||||
|
var current = templates.find { it.id == commandTemplateCard.tag }
|
||||||
|
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)
|
||||||
|
populateCommandCard(commandTemplateCard, it)
|
||||||
|
downloadItem.format = Format(
|
||||||
|
it.title,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
0,
|
||||||
|
it.content
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
shortcutClicked = {
|
||||||
|
UiUtil.showShortcuts(requireActivity(), commandTemplateViewModel,
|
||||||
|
itemSelected = {
|
||||||
|
chosenCommandView.editText!!.setText("${chosenCommandView.editText!!.text} $it")
|
||||||
|
downloadItem.format.format_note = chosenCommandView.editText!!.text.toString()
|
||||||
|
},
|
||||||
|
itemRemoved = {removed ->
|
||||||
|
chosenCommandView.editText!!.setText(chosenCommandView.editText!!.text.replace("(${
|
||||||
|
Regex.escape(
|
||||||
|
removed
|
||||||
|
)
|
||||||
|
})(?!.*\\1)".toRegex(), "").trimEnd())
|
||||||
|
downloadItem.format.format_note = chosenCommandView.editText!!.text.toString().trimEnd()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import androidx.work.Data
|
||||||
import androidx.work.ExistingWorkPolicy
|
import androidx.work.ExistingWorkPolicy
|
||||||
import androidx.work.OneTimeWorkRequestBuilder
|
import androidx.work.OneTimeWorkRequestBuilder
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
|
import com.deniscerri.ytdlnis.MainActivity
|
||||||
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.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
|
|
@ -55,9 +56,8 @@ 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.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
@ -506,6 +506,18 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
downloadViewModel.uiState.collectLatest { res ->
|
||||||
|
if (res.errorMessage != null) {
|
||||||
|
UiUtil.handleDownloadsResponse(requireActivity() as MainActivity, res, downloadViewModel)
|
||||||
|
downloadViewModel.uiState.value = DownloadViewModel.DownloadsUiState(
|
||||||
|
errorMessage = null,
|
||||||
|
actions = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateFileSize(items: List<Long>){
|
private fun updateFileSize(items: List<Long>){
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package com.deniscerri.ytdlnis.ui.downloadcard
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.SharedPreferences
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
|
|
@ -13,9 +14,11 @@ import android.view.ViewGroup
|
||||||
import android.widget.AdapterView
|
import android.widget.AdapterView
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import android.widget.AutoCompleteTextView
|
import android.widget.AutoCompleteTextView
|
||||||
|
import android.widget.LinearLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
|
import androidx.core.view.isVisible
|
||||||
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
|
||||||
|
|
@ -44,7 +47,8 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
|
||||||
private var activity: Activity? = null
|
private var activity: Activity? = null
|
||||||
private lateinit var downloadViewModel : DownloadViewModel
|
private lateinit var downloadViewModel : DownloadViewModel
|
||||||
private lateinit var resultViewModel: ResultViewModel
|
private lateinit var resultViewModel: ResultViewModel
|
||||||
|
private lateinit var preferences: SharedPreferences
|
||||||
|
private lateinit var shownFields: List<String>
|
||||||
lateinit var title : TextInputLayout
|
lateinit var title : TextInputLayout
|
||||||
lateinit var author : TextInputLayout
|
lateinit var author : TextInputLayout
|
||||||
private lateinit var saveDir : TextInputLayout
|
private lateinit var saveDir : TextInputLayout
|
||||||
|
|
@ -54,6 +58,7 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
|
||||||
|
|
||||||
lateinit var downloadItem: DownloadItem
|
lateinit var downloadItem: DownloadItem
|
||||||
|
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
|
|
@ -65,6 +70,8 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
|
||||||
resultViewModel = ViewModelProvider(this@DownloadVideoFragment)[ResultViewModel::class.java]
|
resultViewModel = ViewModelProvider(this@DownloadVideoFragment)[ResultViewModel::class.java]
|
||||||
infoUtil = InfoUtil(requireContext())
|
infoUtil = InfoUtil(requireContext())
|
||||||
genericVideoFormats = infoUtil.getGenericVideoFormats(requireContext().resources)
|
genericVideoFormats = infoUtil.getGenericVideoFormats(requireContext().resources)
|
||||||
|
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
|
shownFields = preferences.getStringSet("modify_download_card", setOf())!!.toList()
|
||||||
return fragmentView
|
return fragmentView
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,6 +92,7 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
|
||||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
try {
|
try {
|
||||||
title = view.findViewById(R.id.title_textinput)
|
title = view.findViewById(R.id.title_textinput)
|
||||||
|
title.visibility = if (shownFields.contains("title")) View.VISIBLE else View.GONE
|
||||||
if (title.editText?.text?.isEmpty() == true){
|
if (title.editText?.text?.isEmpty() == true){
|
||||||
title.editText!!.setText(downloadItem.title)
|
title.editText!!.setText(downloadItem.title)
|
||||||
}
|
}
|
||||||
|
|
@ -97,6 +105,7 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
|
||||||
})
|
})
|
||||||
|
|
||||||
author = view.findViewById(R.id.author_textinput)
|
author = view.findViewById(R.id.author_textinput)
|
||||||
|
author.visibility = if (shownFields.contains("author")) View.VISIBLE else View.GONE
|
||||||
if (author.editText?.text?.isEmpty() == true){
|
if (author.editText?.text?.isEmpty() == true){
|
||||||
author.editText!!.setText(downloadItem.author)
|
author.editText!!.setText(downloadItem.author)
|
||||||
}
|
}
|
||||||
|
|
@ -149,9 +158,12 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
|
||||||
if (currentDownloadItem!!.type != Type.video){
|
if (currentDownloadItem!!.type != Type.video){
|
||||||
downloadItem.type = Type.video
|
downloadItem.type = Type.video
|
||||||
runCatching {
|
runCatching {
|
||||||
downloadItem.format =
|
downloadItem.format = downloadViewModel.getFormat(downloadItem.allFormats, Type.video)
|
||||||
downloadItem.allFormats.filter { it.vcodec.isNotEmpty() }
|
if (downloadItem.videoPreferences.audioFormatIDs.isEmpty()){
|
||||||
.maxByOrNull { it.filesize }!!
|
downloadItem.videoPreferences.audioFormatIDs.add(
|
||||||
|
downloadViewModel.getFormat(downloadItem.allFormats, Type.audio).format_id
|
||||||
|
)
|
||||||
|
}
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
downloadItem.format = genericVideoFormats.last()
|
downloadItem.format = genericVideoFormats.last()
|
||||||
}
|
}
|
||||||
|
|
@ -161,6 +173,7 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
|
||||||
|
|
||||||
val containers = requireContext().resources.getStringArray(R.array.video_containers)
|
val containers = requireContext().resources.getStringArray(R.array.video_containers)
|
||||||
val container = view.findViewById<TextInputLayout>(R.id.downloadContainer)
|
val container = view.findViewById<TextInputLayout>(R.id.downloadContainer)
|
||||||
|
container.visibility = if (shownFields.contains("container")) View.VISIBLE else View.GONE
|
||||||
val containerAutoCompleteTextView =
|
val containerAutoCompleteTextView =
|
||||||
view.findViewById<AutoCompleteTextView>(R.id.container_textview)
|
view.findViewById<AutoCompleteTextView>(R.id.container_textview)
|
||||||
var containerPreference = sharedPreferences.getString("video_format", "Default")
|
var containerPreference = sharedPreferences.getString("video_format", "Default")
|
||||||
|
|
@ -185,6 +198,10 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
|
||||||
resultItem?.formats?.addAll(allFormats.first().filter { !genericVideoFormats.contains(it) })
|
resultItem?.formats?.addAll(allFormats.first().filter { !genericVideoFormats.contains(it) })
|
||||||
if (resultItem != null){
|
if (resultItem != null){
|
||||||
resultViewModel.update(resultItem!!)
|
resultViewModel.update(resultItem!!)
|
||||||
|
kotlin.runCatching {
|
||||||
|
val f1 = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
|
||||||
|
f1.updateUI(resultItem)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -226,61 +243,66 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
UiUtil.configureVideo(
|
view.findViewById<LinearLayout>(R.id.adjust).apply {
|
||||||
view,
|
visibility = if (shownFields.contains("adjust_video")) View.VISIBLE else View.GONE
|
||||||
requireActivity(),
|
if (isVisible){
|
||||||
listOf(downloadItem),
|
UiUtil.configureVideo(
|
||||||
embedSubsClicked = {
|
view,
|
||||||
downloadItem.videoPreferences.embedSubs = it
|
requireActivity(),
|
||||||
},
|
listOf(downloadItem),
|
||||||
addChaptersClicked = {
|
embedSubsClicked = {
|
||||||
downloadItem.videoPreferences.addChapters = it
|
downloadItem.videoPreferences.embedSubs = it
|
||||||
},
|
},
|
||||||
splitByChaptersClicked = {
|
addChaptersClicked = {
|
||||||
downloadItem.videoPreferences.splitByChapters = it
|
downloadItem.videoPreferences.addChapters = it
|
||||||
},
|
},
|
||||||
saveThumbnailClicked = {
|
splitByChaptersClicked = {
|
||||||
downloadItem.SaveThumb = it
|
downloadItem.videoPreferences.splitByChapters = it
|
||||||
},
|
},
|
||||||
sponsorBlockItemsSet = { values, checkedItems ->
|
saveThumbnailClicked = {
|
||||||
downloadItem.videoPreferences.sponsorBlockFilters.clear()
|
downloadItem.SaveThumb = it
|
||||||
for (i in checkedItems.indices) {
|
},
|
||||||
if (checkedItems[i]) {
|
sponsorBlockItemsSet = { values, checkedItems ->
|
||||||
downloadItem.videoPreferences.sponsorBlockFilters.add(values[i])
|
downloadItem.videoPreferences.sponsorBlockFilters.clear()
|
||||||
}
|
for (i in checkedItems.indices) {
|
||||||
}
|
if (checkedItems[i]) {
|
||||||
},
|
downloadItem.videoPreferences.sponsorBlockFilters.add(values[i])
|
||||||
cutClicked = { cutVideoListener ->
|
}
|
||||||
if (parentFragmentManager.findFragmentByTag("cutVideoSheet") == null){
|
}
|
||||||
val bottomSheet = CutVideoBottomSheetDialog(downloadItem, resultItem?.urls ?: "", resultItem?.chapters ?: listOf(), cutVideoListener)
|
},
|
||||||
bottomSheet.show(parentFragmentManager, "cutVideoSheet")
|
cutClicked = { cutVideoListener ->
|
||||||
}
|
if (parentFragmentManager.findFragmentByTag("cutVideoSheet") == null){
|
||||||
},
|
val bottomSheet = CutVideoBottomSheetDialog(downloadItem, resultItem?.urls ?: "", resultItem?.chapters ?: listOf(), cutVideoListener)
|
||||||
filenameTemplateSet = {
|
bottomSheet.show(parentFragmentManager, "cutVideoSheet")
|
||||||
downloadItem.customFileNameTemplate = it
|
}
|
||||||
},
|
},
|
||||||
saveSubtitlesClicked = {
|
filenameTemplateSet = {
|
||||||
downloadItem.videoPreferences.writeSubs = it
|
downloadItem.customFileNameTemplate = it
|
||||||
},
|
},
|
||||||
subtitleLanguagesClicked = {
|
saveSubtitlesClicked = {
|
||||||
UiUtil.showSubtitleLanguagesDialog(requireActivity(), downloadItem.videoPreferences.subsLanguages){
|
downloadItem.videoPreferences.writeSubs = it
|
||||||
downloadItem.videoPreferences.subsLanguages = it
|
},
|
||||||
}
|
subtitleLanguagesClicked = {
|
||||||
},
|
UiUtil.showSubtitleLanguagesDialog(requireActivity(), downloadItem.videoPreferences.subsLanguages){
|
||||||
removeAudioClicked = {
|
downloadItem.videoPreferences.subsLanguages = it
|
||||||
downloadItem.videoPreferences.removeAudio = it
|
}
|
||||||
},
|
},
|
||||||
extraCommandsClicked = {
|
removeAudioClicked = {
|
||||||
val callback = object : ExtraCommandsListener {
|
downloadItem.videoPreferences.removeAudio = it
|
||||||
override fun onChangeExtraCommand(c: String) {
|
},
|
||||||
downloadItem.extraCommands = c
|
extraCommandsClicked = {
|
||||||
}
|
val callback = object : ExtraCommandsListener {
|
||||||
}
|
override fun onChangeExtraCommand(c: String) {
|
||||||
|
downloadItem.extraCommands = c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val bottomSheetDialog = AddExtraCommandsDialog(downloadItem, callback)
|
val bottomSheetDialog = AddExtraCommandsDialog(downloadItem, callback)
|
||||||
bottomSheetDialog.show(parentFragmentManager, "extraCommands")
|
bottomSheetDialog.show(parentFragmentManager, "extraCommands")
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ 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
|
||||||
|
|
@ -28,7 +27,6 @@ import com.google.android.material.button.MaterialButton
|
||||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
import com.yausername.youtubedl_android.YoutubeDL
|
import com.yausername.youtubedl_android.YoutubeDL
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.async
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
fragmentView = inflater.inflate(R.layout.fragment_generic_download_queue, container, false)
|
fragmentView = inflater.inflate(R.layout.generic_recyclerview, container, false)
|
||||||
activity = getActivity()
|
activity = getActivity()
|
||||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||||
return fragmentView
|
return fragmentView
|
||||||
|
|
@ -100,7 +100,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
|
||||||
downloadViewModel.getItemByID(itemID)
|
downloadViewModel.getItemByID(itemID)
|
||||||
}
|
}
|
||||||
withContext(Dispatchers.IO){
|
withContext(Dispatchers.IO){
|
||||||
downloadViewModel.queueDownloads(listOf(item))
|
downloadViewModel.queueDownloads(listOf(item), true)
|
||||||
}
|
}
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Toast.makeText(requireContext(), getString(R.string.error_restarting_download), Toast.LENGTH_LONG).show()
|
Toast.makeText(requireContext(), getString(R.string.error_restarting_download), Toast.LENGTH_LONG).show()
|
||||||
|
|
@ -264,6 +264,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
|
||||||
|
|
||||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||||
val itemID = viewHolder.itemView.tag.toString().toLong()
|
val itemID = viewHolder.itemView.tag.toString().toLong()
|
||||||
|
val position = viewHolder.bindingAdapterPosition
|
||||||
when (direction) {
|
when (direction) {
|
||||||
ItemTouchHelper.LEFT -> {
|
ItemTouchHelper.LEFT -> {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
|
|
@ -279,6 +280,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
|
||||||
}
|
}
|
||||||
ItemTouchHelper.RIGHT -> {
|
ItemTouchHelper.RIGHT -> {
|
||||||
onActionButtonClick(itemID)
|
onActionButtonClick(itemID)
|
||||||
|
adapter.notifyItemChanged(position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
fragmentView = inflater.inflate(R.layout.fragment_generic_download_queue, container, false)
|
fragmentView = inflater.inflate(R.layout.generic_recyclerview, container, false)
|
||||||
activity = getActivity()
|
activity = getActivity()
|
||||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||||
return fragmentView
|
return fragmentView
|
||||||
|
|
@ -269,10 +269,15 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
|
||||||
|
|
||||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||||
val itemID = viewHolder.itemView.tag.toString().toLong()
|
val itemID = viewHolder.itemView.tag.toString().toLong()
|
||||||
|
val position = viewHolder.bindingAdapterPosition
|
||||||
when (direction) {
|
when (direction) {
|
||||||
ItemTouchHelper.RIGHT -> {
|
ItemTouchHelper.RIGHT -> {
|
||||||
runBlocking{
|
lifecycleScope.launch {
|
||||||
downloadViewModel.reQueueDownloadItems(listOf(itemID))
|
val item = withContext(Dispatchers.IO){
|
||||||
|
downloadViewModel.getItemByID(itemID)
|
||||||
|
}
|
||||||
|
downloadViewModel.queueDownloads(listOf(item), true)
|
||||||
|
adapter.notifyItemChanged(position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ItemTouchHelper.LEFT -> {
|
ItemTouchHelper.LEFT -> {
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ import android.view.View.GONE
|
||||||
import android.view.View.VISIBLE
|
import android.view.View.VISIBLE
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.Window
|
import android.view.Window
|
||||||
import android.widget.Button
|
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.RelativeLayout
|
import android.widget.RelativeLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
|
@ -41,28 +40,22 @@ import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.adapter.HistoryAdapter
|
import com.deniscerri.ytdlnis.adapter.HistoryAdapter
|
||||||
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||||
import com.deniscerri.ytdlnis.database.repository.HistoryRepository
|
import com.deniscerri.ytdlnis.database.repository.HistoryRepository
|
||||||
import com.deniscerri.ytdlnis.database.repository.HistoryRepository.HistorySort
|
import com.deniscerri.ytdlnis.database.DBManager.SORTING
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel
|
||||||
import com.deniscerri.ytdlnis.databinding.FragmentHistoryBinding
|
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.UiUtil
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
|
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
|
||||||
import com.google.android.material.button.MaterialButton
|
|
||||||
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
|
||||||
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 it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import java.io.File
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Calendar
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A fragment representing a list of Items.
|
* A fragment representing a list of Items.
|
||||||
|
|
@ -163,8 +156,8 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
||||||
historyViewModel.sortOrder.observe(viewLifecycleOwner){
|
historyViewModel.sortOrder.observe(viewLifecycleOwner){
|
||||||
if (it != null){
|
if (it != null){
|
||||||
when(it){
|
when(it){
|
||||||
HistorySort.ASC -> sortChip.chipIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_down)
|
SORTING.ASC -> sortChip.chipIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_down)
|
||||||
HistorySort.DESC -> sortChip.chipIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_up)
|
SORTING.DESC -> sortChip.chipIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_up)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -277,12 +270,12 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun changeSortIcon(item: TextView, order: HistorySort){
|
private fun changeSortIcon(item: TextView, order: SORTING){
|
||||||
when(order){
|
when(order){
|
||||||
HistorySort.DESC ->{
|
SORTING.DESC ->{
|
||||||
item.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_up, 0,0,0)
|
item.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_up, 0,0,0)
|
||||||
}
|
}
|
||||||
HistorySort.ASC -> {
|
SORTING.ASC -> {
|
||||||
item.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_down, 0,0,0)
|
item.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_down, 0,0,0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -405,146 +398,23 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
||||||
requireView().findViewById<View>(R.id.website_divider).visibility = VISIBLE
|
requireView().findViewById<View>(R.id.website_divider).visibility = VISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun removeItem(item: HistoryItem) {
|
|
||||||
if (bottomSheet != null) bottomSheet!!.hide()
|
|
||||||
val deleteFile = booleanArrayOf(false)
|
|
||||||
val deleteDialog = MaterialAlertDialogBuilder(fragmentContext!!)
|
|
||||||
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + item.title + "\"!")
|
|
||||||
val path = item.downloadPath
|
|
||||||
val file = File(path)
|
|
||||||
if (file.exists() && path.isNotEmpty()) {
|
|
||||||
deleteDialog.setMultiChoiceItems(
|
|
||||||
arrayOf(getString(R.string.delete_file_too)),
|
|
||||||
booleanArrayOf(false)
|
|
||||||
) { _: DialogInterface?, _: Int, b: Boolean -> deleteFile[0] = b }
|
|
||||||
}
|
|
||||||
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
|
||||||
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
|
||||||
historyViewModel.delete(item, deleteFile[0])
|
|
||||||
}
|
|
||||||
deleteDialog.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
override fun onCardClick(itemID: Long, isPresent: Boolean) {
|
override fun onCardClick(itemID: Long, isPresent: Boolean) {
|
||||||
bottomSheet = BottomSheetDialog(fragmentContext!!)
|
|
||||||
bottomSheet!!.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
|
||||||
bottomSheet!!.setContentView(R.layout.history_item_details_bottom_sheet)
|
|
||||||
val item = findItem(itemID)
|
val item = findItem(itemID)
|
||||||
val title = bottomSheet!!.findViewById<TextView>(R.id.bottom_sheet_title)
|
UiUtil.showHistoryItemDetailsCard(item, requireActivity(), isPresent,
|
||||||
title!!.text = item!!.title
|
redownloadItem = {
|
||||||
val author = bottomSheet!!.findViewById<TextView>(R.id.bottom_sheet_author)
|
val downloadItem = downloadViewModel.createDownloadItemFromHistory(it)
|
||||||
author!!.text = item.author
|
runBlocking{
|
||||||
|
downloadViewModel.queueDownloads(listOf(downloadItem))
|
||||||
// BUTTON ----------------------------------
|
}
|
||||||
val btn = bottomSheet!!.findViewById<MaterialButton>(R.id.downloads_download_button_type)
|
historyViewModel.delete(it, false)
|
||||||
|
|
||||||
if (item.type == DownloadViewModel.Type.audio) {
|
|
||||||
if (isPresent) btn!!.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_music_downloaded) else btn!!.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_music)
|
|
||||||
} else if (item.type == DownloadViewModel.Type.video) {
|
|
||||||
if (isPresent) btn!!.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_video_downloaded) else btn!!.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_video)
|
|
||||||
}else{
|
|
||||||
btn!!.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_terminal)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isPresent){
|
|
||||||
btn.setOnClickListener {
|
|
||||||
UiUtil!!.shareFileIntent(requireContext(), listOf(item.downloadPath))
|
|
||||||
}
|
}
|
||||||
}
|
) {
|
||||||
|
|
||||||
val time = bottomSheet!!.findViewById<TextView>(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)
|
|
||||||
|
|
||||||
val calendar = Calendar.getInstance()
|
|
||||||
calendar.timeInMillis = item.time * 1000L
|
|
||||||
time!!.text = SimpleDateFormat(android.text.format.DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(calendar.time)
|
|
||||||
time.isClickable = false
|
|
||||||
|
|
||||||
if (item.format.format_note == "?" || item.format.format_note == "") formatNote!!.visibility = GONE
|
|
||||||
else formatNote!!.text = item.format.format_note
|
|
||||||
|
|
||||||
if (item.format.container != "") container!!.text = item.format.container.uppercase()
|
|
||||||
else container!!.visibility = 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 = GONE
|
|
||||||
}else{
|
|
||||||
codec!!.visibility = VISIBLE
|
|
||||||
codec.text = codecText
|
|
||||||
}
|
|
||||||
|
|
||||||
val file = File(item.downloadPath)
|
|
||||||
val fileSizeReadable = FileUtil.convertFileSize(if (file.exists()) file.length() else item.format.filesize)
|
|
||||||
if (fileSizeReadable == "?") fileSize!!.visibility = 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)
|
|
||||||
}
|
|
||||||
val openFile = bottomSheet!!.findViewById<Button>(R.id.bottomsheet_open_file_button)
|
|
||||||
openFile!!.tag = itemID
|
|
||||||
openFile.setOnClickListener{
|
|
||||||
UiUtil.openFileIntent(requireContext(), item.downloadPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
val redownload = bottomSheet!!.findViewById<Button>(R.id.bottomsheet_redownload_button)
|
|
||||||
redownload!!.tag = itemID
|
|
||||||
redownload.setOnClickListener{
|
|
||||||
val downloadItem = downloadViewModel.createDownloadItemFromHistory(item)
|
|
||||||
runBlocking{
|
|
||||||
downloadViewModel.queueDownloads(listOf(downloadItem))
|
|
||||||
}
|
|
||||||
historyViewModel.delete(item, false)
|
|
||||||
bottomSheet!!.cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
redownload.setOnLongClickListener {
|
|
||||||
bottomSheet!!.cancel()
|
|
||||||
val sheet = DownloadBottomSheetDialog(
|
val sheet = DownloadBottomSheetDialog(
|
||||||
result = downloadViewModel.createResultItemFromHistory(item),
|
result = downloadViewModel.createResultItemFromHistory(it),
|
||||||
type = item.type)
|
type = it.type
|
||||||
|
)
|
||||||
sheet.show(parentFragmentManager, "downloadSingleSheet")
|
sheet.show(parentFragmentManager, "downloadSingleSheet")
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isPresent) openFile.visibility = GONE
|
|
||||||
else redownload.visibility = 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(itemID: Long, isChecked: Boolean) {
|
||||||
|
|
@ -666,7 +536,10 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
||||||
ItemTouchHelper.LEFT -> {
|
ItemTouchHelper.LEFT -> {
|
||||||
val deletedItem = historyList!![position]
|
val deletedItem = historyList!![position]
|
||||||
historyAdapter!!.notifyItemChanged(position)
|
historyAdapter!!.notifyItemChanged(position)
|
||||||
removeItem(deletedItem!!)
|
UiUtil.showRemoveHistoryItemDialog(deletedItem!!, requireActivity(),
|
||||||
|
delete = { item, deleteFile ->
|
||||||
|
historyViewModel.delete(item, deleteFile)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
ItemTouchHelper.RIGHT -> {
|
ItemTouchHelper.RIGHT -> {
|
||||||
val item = historyList!![position]!!
|
val item = historyList!![position]!!
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
fragmentView = inflater.inflate(R.layout.fragment_generic_download_queue, container, false)
|
fragmentView = inflater.inflate(R.layout.generic_recyclerview, container, false)
|
||||||
activity = getActivity()
|
activity = getActivity()
|
||||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||||
return fragmentView
|
return fragmentView
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,19 @@ 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.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.text.InputType
|
||||||
|
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.ViewGroup
|
||||||
|
import android.view.Window
|
||||||
import android.widget.RelativeLayout
|
import android.widget.RelativeLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.appcompat.widget.SearchView
|
||||||
|
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
|
||||||
|
|
@ -21,9 +29,12 @@ import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.adapter.TemplatesAdapter
|
import com.deniscerri.ytdlnis.adapter.TemplatesAdapter
|
||||||
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.database.repository.CommandTemplateRepository.CommandTemplateSortType
|
||||||
|
import com.deniscerri.ytdlnis.database.DBManager.SORTING
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
|
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.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
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
|
||||||
|
|
@ -41,6 +52,7 @@ class CommandTemplatesFragment : Fragment(), TemplatesAdapter.OnItemClickListene
|
||||||
private lateinit var templatesList: List<CommandTemplate>
|
private lateinit var templatesList: List<CommandTemplate>
|
||||||
private lateinit var noResults: RelativeLayout
|
private lateinit var noResults: RelativeLayout
|
||||||
private lateinit var mainActivity: MainActivity
|
private lateinit var mainActivity: MainActivity
|
||||||
|
private lateinit var sortChip: Chip
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
|
|
@ -58,6 +70,7 @@ class CommandTemplatesFragment : Fragment(), TemplatesAdapter.OnItemClickListene
|
||||||
topAppBar = view.findViewById(R.id.logs_toolbar)
|
topAppBar = view.findViewById(R.id.logs_toolbar)
|
||||||
topAppBar.setNavigationOnClickListener { mainActivity.onBackPressedDispatcher.onBackPressed() }
|
topAppBar.setNavigationOnClickListener { mainActivity.onBackPressedDispatcher.onBackPressed() }
|
||||||
noResults = view.findViewById(R.id.no_results)
|
noResults = view.findViewById(R.id.no_results)
|
||||||
|
sortChip = view.findViewById(R.id.sortChip)
|
||||||
|
|
||||||
templatesAdapter =
|
templatesAdapter =
|
||||||
TemplatesAdapter(
|
TemplatesAdapter(
|
||||||
|
|
@ -75,35 +88,90 @@ class CommandTemplatesFragment : Fragment(), TemplatesAdapter.OnItemClickListene
|
||||||
|
|
||||||
|
|
||||||
commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java]
|
commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java]
|
||||||
commandTemplateViewModel.items.observe(viewLifecycleOwner) {
|
commandTemplateViewModel.allItems.observe(viewLifecycleOwner) {
|
||||||
if (it.isEmpty()) noResults.visibility = View.VISIBLE
|
if (it.isEmpty()) noResults.visibility = View.VISIBLE
|
||||||
else noResults.visibility = View.GONE
|
else noResults.visibility = View.GONE
|
||||||
templatesList = it
|
|
||||||
templatesAdapter.submitList(it)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
commandTemplateViewModel.getFilteredList().observe(viewLifecycleOwner) {
|
||||||
|
templatesAdapter.submitList(it)
|
||||||
|
templatesList = it
|
||||||
|
scrollToTop()
|
||||||
|
}
|
||||||
|
|
||||||
|
commandTemplateViewModel.sortOrder.observe(viewLifecycleOwner){
|
||||||
|
if (it != null){
|
||||||
|
when(it){
|
||||||
|
SORTING.ASC -> sortChip.chipIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_down)
|
||||||
|
SORTING.DESC -> sortChip.chipIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_up)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commandTemplateViewModel.sortType.observe(viewLifecycleOwner){
|
||||||
|
if(it != null){
|
||||||
|
when(it){
|
||||||
|
CommandTemplateSortType.DATE -> sortChip.text = getString(R.string.date_added)
|
||||||
|
CommandTemplateSortType.TITLE -> sortChip.text = getString(R.string.title)
|
||||||
|
CommandTemplateSortType.LENGTH -> sortChip.text = getString(R.string.length)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
initMenu()
|
initMenu()
|
||||||
initChips()
|
initChips()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initMenu() {
|
private fun initMenu() {
|
||||||
topAppBar.setOnMenuItemClickListener { m: MenuItem ->
|
val onActionExpandListener: MenuItem.OnActionExpandListener =
|
||||||
val itemId = m.itemId
|
object : MenuItem.OnActionExpandListener {
|
||||||
if (itemId == R.id.export_clipboard) {
|
override fun onMenuItemActionExpand(menuItem: MenuItem): Boolean {
|
||||||
lifecycleScope.launch{
|
return true
|
||||||
withContext(Dispatchers.IO){
|
|
||||||
commandTemplateViewModel.exportToClipboard()
|
|
||||||
}
|
|
||||||
Snackbar.make(recyclerView, getString(R.string.copied_to_clipboard), Snackbar.LENGTH_LONG).show()
|
|
||||||
}
|
}
|
||||||
}else if (itemId == R.id.import_clipboard){
|
|
||||||
lifecycleScope.launch{
|
|
||||||
withContext(Dispatchers.IO){
|
|
||||||
val count = commandTemplateViewModel.importFromClipboard()
|
|
||||||
mainActivity.runOnUiThread{
|
|
||||||
Snackbar.make(recyclerView, "${getString(R.string.items_imported)} (${count})", Snackbar.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
override fun onMenuItemActionCollapse(menuItem: MenuItem): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
topAppBar.menu.findItem(R.id.search_command)
|
||||||
|
.setOnActionExpandListener(onActionExpandListener)
|
||||||
|
val searchView = topAppBar.menu.findItem(R.id.search_command).actionView as SearchView?
|
||||||
|
searchView!!.inputType = InputType.TYPE_CLASS_TEXT
|
||||||
|
searchView.queryHint = getString(R.string.search_command_hint)
|
||||||
|
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||||
|
override fun onQueryTextSubmit(query: String): Boolean {
|
||||||
|
topAppBar.menu.findItem(R.id.search_command).collapseActionView()
|
||||||
|
commandTemplateViewModel.setQueryFilter(query)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onQueryTextChange(newText: String): Boolean {
|
||||||
|
commandTemplateViewModel.setQueryFilter(newText)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
topAppBar.setOnClickListener { scrollToTop() }
|
||||||
|
|
||||||
|
topAppBar.setOnMenuItemClickListener { m: MenuItem ->
|
||||||
|
when(m.itemId){
|
||||||
|
R.id.export_clipboard -> {
|
||||||
|
lifecycleScope.launch{
|
||||||
|
withContext(Dispatchers.IO){
|
||||||
|
commandTemplateViewModel.exportToClipboard()
|
||||||
|
}
|
||||||
|
Snackbar.make(recyclerView, getString(R.string.copied_to_clipboard), Snackbar.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
R.id.import_clipboard -> {
|
||||||
|
lifecycleScope.launch{
|
||||||
|
withContext(Dispatchers.IO){
|
||||||
|
val count = commandTemplateViewModel.importFromClipboard()
|
||||||
|
mainActivity.runOnUiThread{
|
||||||
|
Snackbar.make(recyclerView, "${getString(R.string.items_imported)} (${count})", Snackbar.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
|
|
@ -111,6 +179,45 @@ class CommandTemplatesFragment : Fragment(), TemplatesAdapter.OnItemClickListene
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initChips() {
|
private fun initChips() {
|
||||||
|
val sorting = view?.findViewById<Chip>(R.id.sortChip)
|
||||||
|
sorting?.setOnClickListener {
|
||||||
|
val sortSheet = BottomSheetDialog(requireContext())
|
||||||
|
sortSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||||
|
sortSheet.setContentView(R.layout.command_templates_sort_sheet)
|
||||||
|
|
||||||
|
val date = sortSheet.findViewById<TextView>(R.id.date)
|
||||||
|
val title = sortSheet.findViewById<TextView>(R.id.title)
|
||||||
|
val length = sortSheet.findViewById<TextView>(R.id.length)
|
||||||
|
|
||||||
|
val sortOptions = listOf(date!!, title!!, length!!)
|
||||||
|
sortOptions.forEach { it.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty,0,0,0) }
|
||||||
|
when(commandTemplateViewModel.sortType.value!!) {
|
||||||
|
CommandTemplateSortType.DATE -> changeSortIcon(date, commandTemplateViewModel.sortOrder.value!!)
|
||||||
|
CommandTemplateSortType.TITLE -> changeSortIcon(title, commandTemplateViewModel.sortOrder.value!!)
|
||||||
|
CommandTemplateSortType.LENGTH -> changeSortIcon(length, commandTemplateViewModel.sortOrder.value!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
date.setOnClickListener {
|
||||||
|
sortOptions.forEach { it.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty,0,0,0) }
|
||||||
|
commandTemplateViewModel.setSorting(CommandTemplateSortType.DATE)
|
||||||
|
changeSortIcon(date, commandTemplateViewModel.sortOrder.value!!)
|
||||||
|
}
|
||||||
|
title.setOnClickListener {
|
||||||
|
sortOptions.forEach { it.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty,0,0,0) }
|
||||||
|
commandTemplateViewModel.setSorting(CommandTemplateSortType.TITLE)
|
||||||
|
changeSortIcon(title, commandTemplateViewModel.sortOrder.value!!)
|
||||||
|
}
|
||||||
|
length.setOnClickListener {
|
||||||
|
sortOptions.forEach { it.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty,0,0,0) }
|
||||||
|
commandTemplateViewModel.setSorting(CommandTemplateSortType.LENGTH)
|
||||||
|
changeSortIcon(length, commandTemplateViewModel.sortOrder.value!!)
|
||||||
|
}
|
||||||
|
val displayMetrics = DisplayMetrics()
|
||||||
|
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||||
|
sortSheet.behavior.peekHeight = displayMetrics.heightPixels
|
||||||
|
sortSheet.show()
|
||||||
|
}
|
||||||
|
|
||||||
val new = view?.findViewById<Chip>(R.id.newTemplate)
|
val new = view?.findViewById<Chip>(R.id.newTemplate)
|
||||||
new?.setOnClickListener {
|
new?.setOnClickListener {
|
||||||
UiUtil.showCommandTemplateCreationOrUpdatingSheet(null,mainActivity, this, commandTemplateViewModel) {}
|
UiUtil.showCommandTemplateCreationOrUpdatingSheet(null,mainActivity, this, commandTemplateViewModel) {}
|
||||||
|
|
@ -121,8 +228,25 @@ class CommandTemplatesFragment : Fragment(), TemplatesAdapter.OnItemClickListene
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
private fun changeSortIcon(item: TextView, order: SORTING){
|
||||||
private const val TAG = "DownloadLogActivity"
|
when(order){
|
||||||
|
SORTING.DESC ->{
|
||||||
|
item.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_up, 0,0,0)
|
||||||
|
}
|
||||||
|
SORTING.ASC -> {
|
||||||
|
item.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_down, 0,0,0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun scrollToTop() {
|
||||||
|
recyclerView.scrollToPosition(0)
|
||||||
|
Handler(Looper.getMainLooper()).post {
|
||||||
|
(topAppBar.parent as AppBarLayout).setExpanded(
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onItemClick(commandTemplate: CommandTemplate, index: Int) {
|
override fun onItemClick(commandTemplate: CommandTemplate, index: Int) {
|
||||||
|
|
|
||||||
|
|
@ -62,15 +62,6 @@ class MoreFragment : Fragment() {
|
||||||
terminateApp = view.findViewById(R.id.terminate)
|
terminateApp = view.findViewById(R.id.terminate)
|
||||||
settings = view.findViewById(R.id.settings)
|
settings = view.findViewById(R.id.settings)
|
||||||
|
|
||||||
val navHostFragment = parentFragmentManager.findFragmentById(R.id.frame_layout)
|
|
||||||
|
|
||||||
if (mainSharedPreferences.getBoolean("log_downloads", false)) {
|
|
||||||
logs.visibility = View.VISIBLE
|
|
||||||
}else {
|
|
||||||
logs.visibility = View.GONE
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
terminal.setOnClickListener {
|
terminal.setOnClickListener {
|
||||||
val intent = Intent(context, TerminalActivity::class.java)
|
val intent = Intent(context, TerminalActivity::class.java)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,6 @@ class WebViewActivity : BaseActivity() {
|
||||||
generateBtn = toolbar.findViewById(R.id.generate)
|
generateBtn = toolbar.findViewById(R.id.generate)
|
||||||
webViewCompose = findViewById(R.id.webview_compose)
|
webViewCompose = findViewById(R.id.webview_compose)
|
||||||
cookieManager = CookieManager.getInstance()
|
cookieManager = CookieManager.getInstance()
|
||||||
cookieManager.removeAllCookies(null)
|
|
||||||
preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
|
||||||
webViewClient = object : AccompanistWebViewClient() {
|
webViewClient = object : AccompanistWebViewClient() {
|
||||||
|
|
@ -94,7 +93,6 @@ class WebViewActivity : BaseActivity() {
|
||||||
Toast.makeText(this, "Something went wrong", Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, "Something went wrong", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cookieManager.removeAllCookies(null)
|
|
||||||
onBackPressedDispatcher.onBackPressed()
|
onBackPressedDispatcher.onBackPressed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ 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.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.deniscerri.ytdlnis.work.MoveCacheFilesWorker
|
import com.deniscerri.ytdlnis.work.MoveCacheFilesWorker
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
@ -30,8 +31,8 @@ class FolderSettingsFragment : BaseSettingsFragment() {
|
||||||
private var commandPath: Preference? = null
|
private var commandPath: Preference? = null
|
||||||
private var accessAllFiles : Preference? = null
|
private var accessAllFiles : Preference? = null
|
||||||
private var cacheDownloads : Preference? = null
|
private var cacheDownloads : Preference? = null
|
||||||
private var audioFilenameTemplate : EditTextPreference? = null
|
private var audioFilenameTemplate : Preference? = null
|
||||||
private var videoFilenameTemplate : EditTextPreference? = null
|
private var videoFilenameTemplate : Preference? = null
|
||||||
private var clearCache: Preference? = null
|
private var clearCache: Preference? = null
|
||||||
private var moveCache: Preference? = null
|
private var moveCache: Preference? = null
|
||||||
|
|
||||||
|
|
@ -68,7 +69,7 @@ class FolderSettingsFragment : BaseSettingsFragment() {
|
||||||
accessAllFiles!!.isVisible = false
|
accessAllFiles!!.isVisible = false
|
||||||
cacheDownloads!!.isEnabled = true
|
cacheDownloads!!.isEnabled = true
|
||||||
}else{
|
}else{
|
||||||
editor.putBoolean("cache_downloads", false).apply()
|
editor.putBoolean("cache_downloads", true).apply()
|
||||||
cacheDownloads!!.isEnabled = false
|
cacheDownloads!!.isEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,15 +110,30 @@ class FolderSettingsFragment : BaseSettingsFragment() {
|
||||||
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
|
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
|
||||||
val uri = Uri.parse("package:" + requireContext().packageName)
|
val uri = Uri.parse("package:" + requireContext().packageName)
|
||||||
intent.data = uri
|
intent.data = uri
|
||||||
startActivity(intent)
|
accessAllFilesLauncher.launch(intent)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
videoFilenameTemplate?.title = "${getString(R.string.file_name_template)} [${getString(R.string.video)}]"
|
videoFilenameTemplate?.title = "${getString(R.string.file_name_template)} [${getString(R.string.video)}]"
|
||||||
videoFilenameTemplate?.dialogTitle = "${getString(R.string.file_name_template)} [${getString(R.string.video)}]"
|
videoFilenameTemplate?.summary = preferences.getString("file_name_template", "%(uploader)s - %(title)s")
|
||||||
|
|
||||||
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?.summary = preferences.getString("file_name_template_audio", "%(uploader)s - %(title)s")
|
||||||
|
|
||||||
|
videoFilenameTemplate?.setOnPreferenceClickListener {
|
||||||
|
UiUtil.showFilenameTemplateDialog(requireActivity(), videoFilenameTemplate?.summary.toString() ?: "", "${getString(R.string.file_name_template)} [${getString(R.string.video)}]") {
|
||||||
|
editor.putString("file_name_template", it).apply()
|
||||||
|
videoFilenameTemplate?.summary = it
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
audioFilenameTemplate?.setOnPreferenceClickListener {
|
||||||
|
UiUtil.showFilenameTemplateDialog(requireActivity(), audioFilenameTemplate?.summary.toString() ?: "", "${getString(R.string.file_name_template)} [${getString(R.string.audio)}]") {
|
||||||
|
editor.putString("file_name_template_audio", it).apply()
|
||||||
|
audioFilenameTemplate?.summary = it
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
var cacheSize = File(FileUtil.getCachePath(requireContext())).walkBottomUp().fold(0L) { acc, file -> acc + file.length() }
|
var cacheSize = File(FileUtil.getCachePath(requireContext())).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)}"
|
||||||
|
|
@ -207,6 +223,15 @@ class FolderSettingsFragment : BaseSettingsFragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var accessAllFilesLauncher = registerForActivityResult(
|
||||||
|
ActivityResultContracts.StartActivityForResult()
|
||||||
|
) { result ->
|
||||||
|
if (result.resultCode == Activity.RESULT_OK) {
|
||||||
|
accessAllFiles?.isVisible = false
|
||||||
|
cacheDownloads?.isEnabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun changePath(p: Preference?, data: Intent?, requestCode: Int) {
|
private fun changePath(p: Preference?, data: Intent?, requestCode: Int) {
|
||||||
val path = data!!.data.toString()
|
val path = data!!.data.toString()
|
||||||
p!!.summary = FileUtil.formatPath(data.data.toString())
|
p!!.summary = FileUtil.formatPath(data.data.toString())
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.deniscerri.ytdlnis.ui.more.settings
|
package com.deniscerri.ytdlnis.ui.more.settings
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
|
@ -8,6 +9,7 @@ import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.core.os.LocaleListCompat
|
import androidx.core.os.LocaleListCompat
|
||||||
|
|
@ -32,6 +34,7 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
|
||||||
private var highContrast: SwitchPreferenceCompat? = null
|
private var highContrast: SwitchPreferenceCompat? = null
|
||||||
private var locale: ListPreference? = null
|
private var locale: ListPreference? = null
|
||||||
private var showTerminalShareIcon: SwitchPreferenceCompat? = null
|
private var showTerminalShareIcon: SwitchPreferenceCompat? = null
|
||||||
|
private var ignoreBatteryOptimization: Preference? = null
|
||||||
|
|
||||||
private var updateUtil: UpdateUtil? = null
|
private var updateUtil: UpdateUtil? = null
|
||||||
private var activeDownloadCount = 0
|
private var activeDownloadCount = 0
|
||||||
|
|
@ -127,7 +130,7 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
var ignoreBatteryOptimization = findPreference<Preference>("ignore_battery")
|
ignoreBatteryOptimization = findPreference<Preference>("ignore_battery")
|
||||||
val packageName: String = requireContext().packageName
|
val packageName: String = requireContext().packageName
|
||||||
val pm = requireContext().applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager
|
val pm = requireContext().applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||||
if (pm.isIgnoringBatteryOptimizations(packageName)) {
|
if (pm.isIgnoringBatteryOptimizations(packageName)) {
|
||||||
|
|
@ -140,8 +143,16 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
|
||||||
val intent = Intent()
|
val intent = Intent()
|
||||||
intent.action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
|
intent.action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
|
||||||
intent.data = Uri.parse("package:" + requireContext().packageName)
|
intent.data = Uri.parse("package:" + requireContext().packageName)
|
||||||
startActivity(intent)
|
ignoreBatteryLauncher.launch(intent)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var ignoreBatteryLauncher = registerForActivityResult(
|
||||||
|
ActivityResultContracts.StartActivityForResult()
|
||||||
|
) { result ->
|
||||||
|
if (result.resultCode == Activity.RESULT_OK) {
|
||||||
|
ignoreBatteryOptimization?.isVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.util.LayoutDirection
|
import android.util.LayoutDirection
|
||||||
|
import android.util.Log
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
|
|
@ -171,7 +172,7 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
||||||
Environment.getExternalStoragePublicDirectory(
|
Environment.getExternalStoragePublicDirectory(
|
||||||
Environment.DIRECTORY_DOWNLOADS), "YTDLnis_${BuildConfig.VERSION_NAME}_${currentTime.get(
|
Environment.DIRECTORY_DOWNLOADS), "YTDLnis_${BuildConfig.VERSION_NAME}_${currentTime.get(
|
||||||
Calendar.YEAR)}-${currentTime.get(Calendar.MONTH) + 1}-${currentTime.get(
|
Calendar.YEAR)}-${currentTime.get(Calendar.MONTH) + 1}-${currentTime.get(
|
||||||
Calendar.DAY_OF_MONTH)}.json")
|
Calendar.DAY_OF_MONTH)} [${currentTime.get(Calendar.MILLISECOND)}.json")
|
||||||
saveFile.delete()
|
saveFile.delete()
|
||||||
saveFile.createNewFile()
|
saveFile.createNewFile()
|
||||||
saveFile.writeText(Gson().toJson(json))
|
saveFile.writeText(Gson().toJson(json))
|
||||||
|
|
@ -223,15 +224,14 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
||||||
private fun backupSettings(preferences: SharedPreferences) : JsonArray {
|
private fun backupSettings(preferences: SharedPreferences) : JsonArray {
|
||||||
runCatching {
|
runCatching {
|
||||||
val prefs = preferences.all
|
val prefs = preferences.all
|
||||||
|
prefs.remove("app_language")
|
||||||
val arr = JsonArray()
|
val arr = JsonArray()
|
||||||
prefs.forEach {
|
prefs.forEach {
|
||||||
if (it.key != "app_language"){
|
val obj = JsonObject()
|
||||||
val obj = JsonObject()
|
obj.addProperty("key", it.key)
|
||||||
obj.addProperty("key", it.key)
|
obj.addProperty("value", it.value.toString())
|
||||||
obj.addProperty("value", it.value.toString())
|
obj.addProperty("type", it.value!!::class.simpleName)
|
||||||
obj.addProperty("type", it.value!!::class.simpleName)
|
arr.add(obj)
|
||||||
arr.add(obj)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return arr
|
return arr
|
||||||
}
|
}
|
||||||
|
|
@ -398,27 +398,26 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
||||||
PreferenceManager.getDefaultSharedPreferences(requireContext()).edit(commit = true){
|
PreferenceManager.getDefaultSharedPreferences(requireContext()).edit(commit = true){
|
||||||
clear()
|
clear()
|
||||||
val prefs = json.getAsJsonArray("settings")
|
val prefs = json.getAsJsonArray("settings")
|
||||||
val preferencesKeys = preferences.all.map { it.key }
|
|
||||||
prefs.forEach {
|
prefs.forEach {
|
||||||
val key : String = it.asJsonObject.get("key").toString().replace("\"", "")
|
val key : String = it.asJsonObject.get("key").toString().replace("\"", "")
|
||||||
if (preferencesKeys.contains(key)){
|
when(it.asJsonObject.get("type").toString().replace("\"", "")){
|
||||||
when(it.asJsonObject.get("type").toString().replace("\"", "")){
|
"String" -> {
|
||||||
"String" -> {
|
val value = it.asJsonObject.get("value").toString().replace("\"", "")
|
||||||
val value = it.asJsonObject.get("value").toString().replace("\"", "")
|
putString(key, value)
|
||||||
putString(key, value)
|
}
|
||||||
}
|
"Boolean" -> {
|
||||||
"Boolean" -> {
|
val value = it.asJsonObject.get("value").toString().replace("\"", "").toBoolean()
|
||||||
val value = it.asJsonObject.get("value").toString().replace("\"", "").toBoolean()
|
Log.e("REST", value.toString())
|
||||||
putBoolean(key, value)
|
Log.e("REST", key)
|
||||||
}
|
putBoolean(key, value)
|
||||||
"Int" -> {
|
}
|
||||||
val value = it.asJsonObject.get("value").toString().replace("\"", "").toInt()
|
"Int" -> {
|
||||||
putInt(key, value)
|
val value = it.asJsonObject.get("value").toString().replace("\"", "").toInt()
|
||||||
}
|
putInt(key, value)
|
||||||
"HashSet" -> {
|
}
|
||||||
val value = it.asJsonObject.get("value").toString().replace("(\")|(\\[)|(])|([ \\t])".toRegex(), "").split(",")
|
"HashSet" -> {
|
||||||
putStringSet(key, value.toHashSet())
|
val value = it.asJsonObject.get("value").toString().replace("(\")|(\\[)|(])|([ \\t])".toRegex(), "").split(",")
|
||||||
}
|
putStringSet(key, value.toHashSet())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package com.deniscerri.ytdlnis.util
|
package com.deniscerri.ytdlnis.util
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
|
|
@ -16,7 +15,6 @@ import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.models.Format
|
import com.deniscerri.ytdlnis.database.models.Format
|
||||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdlnis.ui.ErrorDialogActivity
|
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import com.yausername.youtubedl_android.YoutubeDL
|
import com.yausername.youtubedl_android.YoutubeDL
|
||||||
|
|
@ -387,17 +385,13 @@ class InfoUtil(private val context: Context) {
|
||||||
val items = arrayListOf<ResultItem?>()
|
val items = arrayListOf<ResultItem?>()
|
||||||
val url = "$pipedURL/trending?region=$countryCODE"
|
val url = "$pipedURL/trending?region=$countryCODE"
|
||||||
val res = genericArrayRequest(url)
|
val res = genericArrayRequest(url)
|
||||||
try {
|
for (i in 0 until res.length()) {
|
||||||
for (i in 0 until res.length()) {
|
val element = res.getJSONObject(i)
|
||||||
val element = res.getJSONObject(i)
|
if (element.getInt("duration") < 0) continue
|
||||||
if (element.getInt("duration") < 0) continue
|
element.put("uploader", element.getString("uploaderName"))
|
||||||
element.put("uploader", element.getString("uploaderName"))
|
val v = createVideoFromPipedJSON(element, "https://youtube.com" + element.getString("url"))
|
||||||
val v = createVideoFromPipedJSON(element, "https://youtube.com" + element.getString("url"))
|
if (v == null || v.thumb.isEmpty()) continue
|
||||||
if (v == null || v.thumb.isEmpty()) continue
|
items.add(v)
|
||||||
items.add(v)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
}
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
@ -440,51 +434,43 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getFormatsFromYTDL(url: String) : List<Format> {
|
private fun getFormatsFromYTDL(url: String) : List<Format> {
|
||||||
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("--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)){
|
||||||
FileUtil.getCookieFile(context){
|
FileUtil.getCookieFile(context){
|
||||||
request.addOption("--cookies", it)
|
request.addOption("--cookies", it)
|
||||||
}
|
|
||||||
|
|
||||||
val useHeader = sharedPreferences.getBoolean("use_header", false)
|
|
||||||
val header = sharedPreferences.getString("useragent_header", "")
|
|
||||||
if (useHeader && !header.isNullOrBlank()){
|
|
||||||
request.addOption("--add-header","User-Agent:${header}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val proxy = sharedPreferences.getString("proxy", "")
|
val useHeader = sharedPreferences.getBoolean("use_header", false)
|
||||||
if (proxy!!.isNotBlank()) {
|
val header = sharedPreferences.getString("useragent_header", "")
|
||||||
request.addOption("--proxy", proxy)
|
if (useHeader && !header.isNullOrBlank()){
|
||||||
}
|
request.addOption("--add-header","User-Agent:${header}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
val res = YoutubeDL.getInstance().execute(request)
|
|
||||||
val results: Array<String?> = try {
|
|
||||||
val lineSeparator = System.getProperty("line.separator")
|
|
||||||
res.out.split(lineSeparator!!).toTypedArray()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
arrayOf(res.out)
|
|
||||||
}
|
|
||||||
val json = results[0]
|
|
||||||
val jsonArray = JSONArray(json)
|
|
||||||
|
|
||||||
return parseYTDLFormats(jsonArray)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
Looper.prepare().run {
|
|
||||||
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return emptyList()
|
|
||||||
|
val proxy = sharedPreferences.getString("proxy", "")
|
||||||
|
if (proxy!!.isNotBlank()) {
|
||||||
|
request.addOption("--proxy", proxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
val res = YoutubeDL.getInstance().execute(request)
|
||||||
|
val results: Array<String?> = try {
|
||||||
|
val lineSeparator = System.getProperty("line.separator")
|
||||||
|
res.out.split(lineSeparator!!).toTypedArray()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
arrayOf(res.out)
|
||||||
|
}
|
||||||
|
val json = results[0]
|
||||||
|
val jsonArray = JSONArray(json)
|
||||||
|
|
||||||
|
return parseYTDLFormats(jsonArray)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFormatsMultiple(urls: List<String>, progress: (progress: List<Format>) -> Unit){
|
fun getFormatsMultiple(urls: List<String>, progress: (progress: List<Format>) -> Unit){
|
||||||
|
|
@ -561,147 +547,139 @@ class InfoUtil(private val context: Context) {
|
||||||
fun getFromYTDL(query: String): ArrayList<ResultItem?> {
|
fun getFromYTDL(query: String): ArrayList<ResultItem?> {
|
||||||
val items = arrayListOf<ResultItem?>()
|
val items = arrayListOf<ResultItem?>()
|
||||||
val searchEngine = sharedPreferences.getString("search_engine", "ytsearch")
|
val searchEngine = sharedPreferences.getString("search_engine", "ytsearch")
|
||||||
try {
|
|
||||||
val request : YoutubeDLRequest
|
val request : YoutubeDLRequest
|
||||||
if (query.contains("http")){
|
if (query.contains("http")){
|
||||||
request = YoutubeDLRequest(query)
|
request = YoutubeDLRequest(query)
|
||||||
|
}else{
|
||||||
|
request = YoutubeDLRequest(emptyList())
|
||||||
|
if (searchEngine == "ytsearchmusic"){
|
||||||
|
request.addOption("--default-search", "https://music.youtube.com/search?q=")
|
||||||
|
request.addOption("ytsearch25:\"${query}\"")
|
||||||
}else{
|
}else{
|
||||||
request = YoutubeDLRequest(emptyList())
|
request.addOption("${searchEngine}25:\"${query}\"")
|
||||||
if (searchEngine == "ytsearchmusic"){
|
}
|
||||||
request.addOption("--default-search", "https://music.youtube.com/search?q=")
|
}
|
||||||
request.addOption("ytsearch25:\"${query}\"")
|
|
||||||
}else{
|
request.addOption("--flat-playlist")
|
||||||
request.addOption("${searchEngine}25:\"${query}\"")
|
request.addOption("-j")
|
||||||
}
|
request.addOption("--skip-download")
|
||||||
|
request.addOption("-R", "1")
|
||||||
|
request.addOption("--socket-timeout", "5")
|
||||||
|
|
||||||
|
if (sharedPreferences.getBoolean("use_cookies", false)){
|
||||||
|
FileUtil.getCookieFile(context){
|
||||||
|
request.addOption("--cookies", it)
|
||||||
}
|
}
|
||||||
|
|
||||||
request.addOption("--flat-playlist")
|
val useHeader = sharedPreferences.getBoolean("use_header", false)
|
||||||
request.addOption("-j")
|
val header = sharedPreferences.getString("useragent_header", "")
|
||||||
request.addOption("--skip-download")
|
if (useHeader && !header.isNullOrBlank()){
|
||||||
request.addOption("-R", "1")
|
request.addOption("--add-header","User-Agent:${header}")
|
||||||
request.addOption("--socket-timeout", "5")
|
|
||||||
|
|
||||||
if (sharedPreferences.getBoolean("use_cookies", false)){
|
|
||||||
FileUtil.getCookieFile(context){
|
|
||||||
request.addOption("--cookies", it)
|
|
||||||
}
|
|
||||||
|
|
||||||
val useHeader = sharedPreferences.getBoolean("use_header", false)
|
|
||||||
val header = sharedPreferences.getString("useragent_header", "")
|
|
||||||
if (useHeader && !header.isNullOrBlank()){
|
|
||||||
request.addOption("--add-header","User-Agent:${header}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val proxy = sharedPreferences.getString("proxy", "")
|
val proxy = sharedPreferences.getString("proxy", "")
|
||||||
if (proxy!!.isNotBlank()){
|
if (proxy!!.isNotBlank()){
|
||||||
request.addOption("--proxy", proxy)
|
request.addOption("--proxy", proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
|
val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
|
||||||
val results: List<String?> = try {
|
val results: List<String?> = try {
|
||||||
val lineSeparator = System.getProperty("line.separator")
|
val lineSeparator = System.getProperty("line.separator")
|
||||||
youtubeDLResponse.out.split(lineSeparator!!)
|
youtubeDLResponse.out.split(lineSeparator!!)
|
||||||
} catch (e: Exception) {
|
|
||||||
listOf(youtubeDLResponse.out)
|
|
||||||
}.filter { it.isNotBlank() }
|
|
||||||
for (result in results) {
|
|
||||||
if (result.isNullOrBlank()) continue
|
|
||||||
val jsonObject = JSONObject(result)
|
|
||||||
val title = if (jsonObject.has("title")) {
|
|
||||||
if (jsonObject.getString("title") == "[Private video]") continue
|
|
||||||
jsonObject.getString("title")
|
|
||||||
} else {
|
|
||||||
jsonObject.getString("webpage_url_basename")
|
|
||||||
}
|
|
||||||
var author: String = if (jsonObject.has("uploader")) jsonObject.getString("uploader") else ""
|
|
||||||
if (author.isEmpty() || author == "null"){
|
|
||||||
author = if (jsonObject.has("channel")) jsonObject.getString("channel") else ""
|
|
||||||
if (author.isEmpty() || author == "null"){
|
|
||||||
author = if (jsonObject.has("playlist_uploader")) jsonObject.getString("playlist_uploader") else ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var duration = ""
|
|
||||||
runCatching {
|
|
||||||
if (jsonObject.has("duration")) {
|
|
||||||
duration = formatIntegerDuration(jsonObject.getInt("duration"), Locale.US)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var thumb: String? = ""
|
|
||||||
if (jsonObject.has("thumbnail")) {
|
|
||||||
thumb = jsonObject.getString("thumbnail")
|
|
||||||
} else if (jsonObject.has("thumbnails")) {
|
|
||||||
val thumbs = jsonObject.getJSONArray("thumbnails")
|
|
||||||
if (thumbs.length() > 0){
|
|
||||||
thumb = thumbs.getJSONObject(thumbs.length() - 1).getString("url")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val website = jsonObject.getString(listOf("ie_key", "extractor_key", "extractor").first { jsonObject.has(it) })
|
|
||||||
var playlistTitle: String? = ""
|
|
||||||
if (jsonObject.has("playlist_title")) playlistTitle = jsonObject.getString("playlist_title")
|
|
||||||
if(playlistTitle.equals(query)) playlistTitle = ""
|
|
||||||
|
|
||||||
if (playlistTitle?.isNotBlank() == true){
|
|
||||||
playlistTitle += "[${jsonObject.getString("playlist_index")}]"
|
|
||||||
}
|
|
||||||
|
|
||||||
val formatsInJSON = if (jsonObject.has("formats") && jsonObject.get("formats") is JSONArray) jsonObject.getJSONArray("formats") else null
|
|
||||||
val formats : ArrayList<Format> = parseYTDLFormats(formatsInJSON)
|
|
||||||
|
|
||||||
val chaptersInJSON = if (jsonObject.has("chapters") && jsonObject.get("chapters") is JSONArray) jsonObject.getJSONArray("chapters") else null
|
|
||||||
val listType: Type = object : TypeToken<List<ChapterItem>>() {}.type
|
|
||||||
var chapters : ArrayList<ChapterItem> = arrayListOf()
|
|
||||||
|
|
||||||
if (chaptersInJSON != null){
|
|
||||||
chapters = Gson().fromJson(chaptersInJSON.toString(), listType)
|
|
||||||
}
|
|
||||||
|
|
||||||
var urls = "";
|
|
||||||
if(jsonObject.has("requested_formats")) {
|
|
||||||
val requestedFormats = jsonObject.getJSONArray("requested_formats")
|
|
||||||
val urlList = mutableListOf<String>()
|
|
||||||
val length = requestedFormats.length()-1
|
|
||||||
for (i in length downTo 0) {
|
|
||||||
urlList.add(requestedFormats.getJSONObject(i).getString("url"))
|
|
||||||
}
|
|
||||||
|
|
||||||
urls = urlList.joinToString("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
val url = if (jsonObject.has("url") && results.size > 1){
|
|
||||||
jsonObject.getString("url")
|
|
||||||
}else{
|
|
||||||
if (Patterns.WEB_URL.matcher(query).matches()){
|
|
||||||
query
|
|
||||||
}else{
|
|
||||||
jsonObject.getString("webpage_url")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
items.add(ResultItem(0,
|
|
||||||
url,
|
|
||||||
title,
|
|
||||||
author,
|
|
||||||
duration,
|
|
||||||
thumb!!,
|
|
||||||
website,
|
|
||||||
playlistTitle!!,
|
|
||||||
formats,
|
|
||||||
urls,
|
|
||||||
chapters
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
val intent = Intent(context, ErrorDialogActivity::class.java)
|
listOf(youtubeDLResponse.out)
|
||||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
}.filter { it.isNotBlank() }
|
||||||
intent.putExtra("title", context.resources.getString(R.string.no_results))
|
for (result in results) {
|
||||||
intent.putExtra("message", e.message)
|
if (result.isNullOrBlank()) continue
|
||||||
context.startActivity(intent)
|
val jsonObject = JSONObject(result)
|
||||||
e.printStackTrace()
|
val title = if (jsonObject.has("title")) {
|
||||||
|
if (jsonObject.getString("title") == "[Private video]") continue
|
||||||
|
jsonObject.getString("title")
|
||||||
|
} else {
|
||||||
|
jsonObject.getString("webpage_url_basename")
|
||||||
|
}
|
||||||
|
var author: String = if (jsonObject.has("uploader")) jsonObject.getString("uploader") else ""
|
||||||
|
if (author.isEmpty() || author == "null"){
|
||||||
|
author = if (jsonObject.has("channel")) jsonObject.getString("channel") else ""
|
||||||
|
if (author.isEmpty() || author == "null"){
|
||||||
|
author = if (jsonObject.has("playlist_uploader")) jsonObject.getString("playlist_uploader") else ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var duration = ""
|
||||||
|
runCatching {
|
||||||
|
if (jsonObject.has("duration")) {
|
||||||
|
duration = formatIntegerDuration(jsonObject.getInt("duration"), Locale.US)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var thumb: String? = ""
|
||||||
|
if (jsonObject.has("thumbnail")) {
|
||||||
|
thumb = jsonObject.getString("thumbnail")
|
||||||
|
} else if (jsonObject.has("thumbnails")) {
|
||||||
|
val thumbs = jsonObject.getJSONArray("thumbnails")
|
||||||
|
if (thumbs.length() > 0){
|
||||||
|
thumb = thumbs.getJSONObject(thumbs.length() - 1).getString("url")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val website = jsonObject.getString(listOf("ie_key", "extractor_key", "extractor").first { jsonObject.has(it) })
|
||||||
|
var playlistTitle: String? = ""
|
||||||
|
if (jsonObject.has("playlist_title")) playlistTitle = jsonObject.getString("playlist_title")
|
||||||
|
if(playlistTitle.equals(query)) playlistTitle = ""
|
||||||
|
|
||||||
|
if (playlistTitle?.isNotBlank() == true){
|
||||||
|
playlistTitle += "[${jsonObject.getString("playlist_index")}]"
|
||||||
|
}
|
||||||
|
|
||||||
|
val formatsInJSON = if (jsonObject.has("formats") && jsonObject.get("formats") is JSONArray) jsonObject.getJSONArray("formats") else null
|
||||||
|
val formats : ArrayList<Format> = parseYTDLFormats(formatsInJSON)
|
||||||
|
|
||||||
|
val chaptersInJSON = if (jsonObject.has("chapters") && jsonObject.get("chapters") is JSONArray) jsonObject.getJSONArray("chapters") else null
|
||||||
|
val listType: Type = object : TypeToken<List<ChapterItem>>() {}.type
|
||||||
|
var chapters : ArrayList<ChapterItem> = arrayListOf()
|
||||||
|
|
||||||
|
if (chaptersInJSON != null){
|
||||||
|
chapters = Gson().fromJson(chaptersInJSON.toString(), listType)
|
||||||
|
}
|
||||||
|
|
||||||
|
var urls = "";
|
||||||
|
if(jsonObject.has("requested_formats")) {
|
||||||
|
val requestedFormats = jsonObject.getJSONArray("requested_formats")
|
||||||
|
val urlList = mutableListOf<String>()
|
||||||
|
val length = requestedFormats.length()-1
|
||||||
|
for (i in length downTo 0) {
|
||||||
|
urlList.add(requestedFormats.getJSONObject(i).getString("url"))
|
||||||
|
}
|
||||||
|
|
||||||
|
urls = urlList.joinToString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
val url = if (jsonObject.has("url") && results.size > 1){
|
||||||
|
jsonObject.getString("url")
|
||||||
|
}else{
|
||||||
|
if (Patterns.WEB_URL.matcher(query).matches()){
|
||||||
|
query
|
||||||
|
}else{
|
||||||
|
jsonObject.getString("webpage_url")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items.add(ResultItem(0,
|
||||||
|
url,
|
||||||
|
title,
|
||||||
|
author,
|
||||||
|
duration,
|
||||||
|
thumb!!,
|
||||||
|
website,
|
||||||
|
playlistTitle!!,
|
||||||
|
formats,
|
||||||
|
urls,
|
||||||
|
chapters
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
@ -748,89 +726,81 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMissingInfo(url: String): ResultItem? {
|
fun getMissingInfo(url: String): ResultItem? {
|
||||||
try {
|
val request = YoutubeDLRequest(url)
|
||||||
val request = YoutubeDLRequest(url)
|
request.addOption("--flat-playlist")
|
||||||
request.addOption("--flat-playlist")
|
request.addOption("-J")
|
||||||
request.addOption("-J")
|
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)){
|
||||||
FileUtil.getCookieFile(context){
|
FileUtil.getCookieFile(context){
|
||||||
request.addOption("--cookies", it)
|
request.addOption("--cookies", it)
|
||||||
}
|
|
||||||
|
|
||||||
val useHeader = sharedPreferences.getBoolean("use_header", false)
|
|
||||||
val header = sharedPreferences.getString("useragent_header", "")
|
|
||||||
if (useHeader && !header.isNullOrBlank()){
|
|
||||||
request.addOption("--add-header","User-Agent:${header}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val useHeader = sharedPreferences.getBoolean("use_header", false)
|
||||||
|
val header = sharedPreferences.getString("useragent_header", "")
|
||||||
val proxy = sharedPreferences.getString("proxy", "")
|
if (useHeader && !header.isNullOrBlank()){
|
||||||
if (proxy!!.isNotBlank()){
|
request.addOption("--add-header","User-Agent:${header}")
|
||||||
request.addOption("--proxy", proxy)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
|
|
||||||
val jsonObject = JSONObject(youtubeDLResponse.out)
|
|
||||||
|
|
||||||
var author: String = if (jsonObject.has("uploader")) jsonObject.getString("uploader") else ""
|
|
||||||
if (author.isEmpty() || author == "null"){
|
|
||||||
author = if (jsonObject.has("channel")) jsonObject.getString("channel") else ""
|
|
||||||
if (author.isEmpty() || author == "null"){
|
|
||||||
author = if (jsonObject.has("playlist_uploader")) jsonObject.getString("playlist_uploader") else ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var duration = ""
|
|
||||||
runCatching {
|
|
||||||
if (jsonObject.has("duration")) {
|
|
||||||
duration = formatIntegerDuration(jsonObject.getInt("duration"), Locale.US)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var thumb: String? = ""
|
|
||||||
if (jsonObject.has("thumbnail")) {
|
|
||||||
thumb = jsonObject.getString("thumbnail")
|
|
||||||
} else if (jsonObject.has("thumbnails")) {
|
|
||||||
val thumbs = jsonObject.getJSONArray("thumbnails")
|
|
||||||
if (thumbs.length() > 0){
|
|
||||||
thumb = thumbs.getJSONObject(thumbs.length() - 1).getString("url")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val isPlaylist = jsonObject.has("playlist_count")
|
|
||||||
return ResultItem(
|
|
||||||
0,
|
|
||||||
url,
|
|
||||||
if (isPlaylist){
|
|
||||||
"[${jsonObject.getInt("playlist_count")} Items] ${jsonObject.getString("title")}"
|
|
||||||
}else{
|
|
||||||
jsonObject.getString("title")
|
|
||||||
},
|
|
||||||
author,
|
|
||||||
duration,
|
|
||||||
thumb!!,
|
|
||||||
jsonObject.getString("extractor"),
|
|
||||||
if (isPlaylist) jsonObject.getString("title") else "",
|
|
||||||
arrayListOf(),
|
|
||||||
"",
|
|
||||||
arrayListOf(),
|
|
||||||
System.currentTimeMillis()
|
|
||||||
)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Looper.prepare().run {
|
|
||||||
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
}
|
||||||
return null
|
|
||||||
|
|
||||||
|
|
||||||
|
val proxy = sharedPreferences.getString("proxy", "")
|
||||||
|
if (proxy!!.isNotBlank()){
|
||||||
|
request.addOption("--proxy", proxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
|
||||||
|
val jsonObject = JSONObject(youtubeDLResponse.out)
|
||||||
|
|
||||||
|
var author: String = if (jsonObject.has("uploader")) jsonObject.getString("uploader") else ""
|
||||||
|
if (author.isEmpty() || author == "null"){
|
||||||
|
author = if (jsonObject.has("channel")) jsonObject.getString("channel") else ""
|
||||||
|
if (author.isEmpty() || author == "null"){
|
||||||
|
author = if (jsonObject.has("playlist_uploader")) jsonObject.getString("playlist_uploader") else ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var duration = ""
|
||||||
|
runCatching {
|
||||||
|
if (jsonObject.has("duration")) {
|
||||||
|
duration = formatIntegerDuration(jsonObject.getInt("duration"), Locale.US)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var thumb: String? = ""
|
||||||
|
if (jsonObject.has("thumbnail")) {
|
||||||
|
thumb = jsonObject.getString("thumbnail")
|
||||||
|
} else if (jsonObject.has("thumbnails")) {
|
||||||
|
val thumbs = jsonObject.getJSONArray("thumbnails")
|
||||||
|
if (thumbs.length() > 0){
|
||||||
|
thumb = thumbs.getJSONObject(thumbs.length() - 1).getString("url")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val isPlaylist = jsonObject.has("playlist_count")
|
||||||
|
return ResultItem(
|
||||||
|
0,
|
||||||
|
url,
|
||||||
|
if (isPlaylist){
|
||||||
|
""
|
||||||
|
}else{
|
||||||
|
jsonObject.getString("title")
|
||||||
|
},
|
||||||
|
author,
|
||||||
|
duration,
|
||||||
|
thumb!!,
|
||||||
|
jsonObject.getString("extractor"),
|
||||||
|
if (isPlaylist) jsonObject.getString("title") else "",
|
||||||
|
arrayListOf(),
|
||||||
|
"",
|
||||||
|
arrayListOf(),
|
||||||
|
System.currentTimeMillis()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -983,9 +953,9 @@ class InfoUtil(private val context: Context) {
|
||||||
val downDir : File
|
val downDir : File
|
||||||
if (!sharedPreferences.getBoolean("cache_downloads", true) && File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite()){
|
if (!sharedPreferences.getBoolean("cache_downloads", true) && File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite()){
|
||||||
downDir = File(FileUtil.formatPath(downloadItem.downloadPath))
|
downDir = File(FileUtil.formatPath(downloadItem.downloadPath))
|
||||||
|
request.addOption("--no-quiet")
|
||||||
|
request.addOption("-no-simulate")
|
||||||
request.addOption("--print", "after_video:'%(filename)s'")
|
request.addOption("--print", "after_video:'%(filename)s'")
|
||||||
request.addOption("--progress")
|
|
||||||
request.addOption("-v")
|
|
||||||
}else{
|
}else{
|
||||||
val cacheDir = FileUtil.getCachePath(context)
|
val cacheDir = FileUtil.getCachePath(context)
|
||||||
downDir = File(cacheDir, downloadItem.id.toString())
|
downDir = File(cacheDir, downloadItem.id.toString())
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,18 @@ package com.deniscerri.ytdlnis.util
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.app.Dialog
|
||||||
import android.content.ClipData
|
import android.content.ClipData
|
||||||
import android.content.ClipboardManager
|
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.Color
|
import android.graphics.Color
|
||||||
import android.graphics.Typeface
|
|
||||||
import android.graphics.drawable.ShapeDrawable
|
import android.graphics.drawable.ShapeDrawable
|
||||||
import android.graphics.drawable.shapes.OvalShape
|
import android.graphics.drawable.shapes.OvalShape
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Looper
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
import android.text.format.DateFormat
|
import android.text.format.DateFormat
|
||||||
|
|
@ -34,22 +36,32 @@ 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.ContextCompat
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
|
import androidx.core.view.allViews
|
||||||
|
import androidx.core.view.children
|
||||||
|
import androidx.core.view.get
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.widget.doOnTextChanged
|
import androidx.core.widget.doOnTextChanged
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.deniscerri.ytdlnis.App
|
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||||
|
import com.deniscerri.ytdlnis.MainActivity
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
|
import com.deniscerri.ytdlnis.adapter.ConfigureMultipleDownloadsAdapter
|
||||||
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.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.models.Format
|
import com.deniscerri.ytdlnis.database.models.Format
|
||||||
|
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||||
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.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.database.viewmodel.DownloadViewModel
|
||||||
|
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
||||||
|
import com.deniscerri.ytdlnis.ui.downloadcard.ConfigureDownloadBottomSheetDialog
|
||||||
|
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
|
||||||
import com.deniscerri.ytdlnis.ui.downloadcard.VideoCutListener
|
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
|
||||||
|
|
@ -61,6 +73,7 @@ import com.google.android.material.datepicker.CalendarConstraints
|
||||||
import com.google.android.material.datepicker.DateValidatorPointForward
|
import com.google.android.material.datepicker.DateValidatorPointForward
|
||||||
import com.google.android.material.datepicker.MaterialDatePicker
|
import com.google.android.material.datepicker.MaterialDatePicker
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
import com.google.android.material.materialswitch.MaterialSwitch
|
import com.google.android.material.materialswitch.MaterialSwitch
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import com.google.android.material.timepicker.MaterialTimePicker
|
import com.google.android.material.timepicker.MaterialTimePicker
|
||||||
|
|
@ -68,7 +81,9 @@ import com.google.android.material.timepicker.TimeFormat
|
||||||
import com.neo.highlight.core.Highlight
|
import com.neo.highlight.core.Highlight
|
||||||
import com.neo.highlight.util.listener.HighlightTextWatcher
|
import com.neo.highlight.util.listener.HighlightTextWatcher
|
||||||
import com.neo.highlight.util.scheme.ColorScheme
|
import com.neo.highlight.util.scheme.ColorScheme
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import me.zhanghai.android.fastscroll.FastScrollerBuilder
|
import me.zhanghai.android.fastscroll.FastScrollerBuilder
|
||||||
|
|
@ -416,17 +431,17 @@ object UiUtil {
|
||||||
author!!.text = item.author.ifEmpty { "`${context.getString(R.string.defaultValue)}`" }
|
author!!.text = item.author.ifEmpty { "`${context.getString(R.string.defaultValue)}`" }
|
||||||
|
|
||||||
// BUTTON ----------------------------------
|
// BUTTON ----------------------------------
|
||||||
val btn = bottomSheet.findViewById<MaterialButton>(R.id.downloads_download_button_type)
|
val btn = bottomSheet.findViewById<FloatingActionButton>(R.id.download_button_type)
|
||||||
|
|
||||||
when (item.type) {
|
when (item.type) {
|
||||||
DownloadViewModel.Type.audio -> {
|
DownloadViewModel.Type.audio -> {
|
||||||
btn!!.icon = ContextCompat.getDrawable(context, R.drawable.ic_music)
|
btn?.setImageResource(R.drawable.ic_music)
|
||||||
}
|
}
|
||||||
DownloadViewModel.Type.video -> {
|
DownloadViewModel.Type.video -> {
|
||||||
btn!!.icon = ContextCompat.getDrawable(context, R.drawable.ic_video)
|
btn?.setImageResource(R.drawable.ic_video)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
btn!!.icon = ContextCompat.getDrawable(context, R.drawable.ic_terminal)
|
btn?.setImageResource(R.drawable.ic_terminal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -542,6 +557,135 @@ object UiUtil {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun showHistoryItemDetailsCard(
|
||||||
|
item: HistoryItem?,
|
||||||
|
context: Activity,
|
||||||
|
isPresent: Boolean,
|
||||||
|
redownloadItem: (HistoryItem) -> Unit,
|
||||||
|
redownloadShowDownloadCard: (HistoryItem) -> 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
|
||||||
|
val author = bottomSheet.findViewById<TextView>(R.id.bottom_sheet_author)
|
||||||
|
author!!.text = item.author
|
||||||
|
|
||||||
|
// BUTTON ----------------------------------
|
||||||
|
val btn = bottomSheet.findViewById<FloatingActionButton>(R.id.download_button_type)
|
||||||
|
|
||||||
|
if (item.type == DownloadViewModel.Type.audio) {
|
||||||
|
if (isPresent) {
|
||||||
|
btn?.setImageResource(R.drawable.ic_music_downloaded)
|
||||||
|
} else {
|
||||||
|
btn?.setImageResource(R.drawable.ic_music)
|
||||||
|
}
|
||||||
|
} else if (item.type == DownloadViewModel.Type.video) {
|
||||||
|
if (isPresent) {
|
||||||
|
btn?.setImageResource(R.drawable.ic_video_downloaded)
|
||||||
|
} else {
|
||||||
|
btn?.setImageResource(R.drawable.ic_video)
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
btn?.setImageResource(R.drawable.ic_terminal)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPresent){
|
||||||
|
btn?.setOnClickListener {
|
||||||
|
shareFileIntent(context, listOf(item.downloadPath))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val time = bottomSheet.findViewById<TextView>(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)
|
||||||
|
|
||||||
|
val calendar = Calendar.getInstance()
|
||||||
|
calendar.timeInMillis = item.time * 1000L
|
||||||
|
time!!.text = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(calendar.time)
|
||||||
|
time.isClickable = false
|
||||||
|
|
||||||
|
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 file = File(item.downloadPath)
|
||||||
|
val fileSizeReadable = FileUtil.convertFileSize(if (file.exists()) file.length() else 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{
|
||||||
|
showRemoveHistoryItemDialog(item, context, delete = { item, deleteFile ->
|
||||||
|
|
||||||
|
})
|
||||||
|
bottomSheet.dismiss()
|
||||||
|
}
|
||||||
|
val openFile = bottomSheet.findViewById<Button>(R.id.bottomsheet_open_file_button)
|
||||||
|
openFile!!.tag = item.id
|
||||||
|
openFile.setOnClickListener{
|
||||||
|
openFileIntent(context, item.downloadPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
val redownload = bottomSheet.findViewById<Button>(R.id.bottomsheet_redownload_button)
|
||||||
|
redownload!!.tag = item.id
|
||||||
|
redownload.setOnClickListener{
|
||||||
|
redownloadItem(item)
|
||||||
|
bottomSheet.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
redownload.setOnLongClickListener {
|
||||||
|
redownloadShowDownloadCard(item)
|
||||||
|
bottomSheet.cancel()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isPresent) openFile.visibility = View.GONE
|
||||||
|
else redownload.visibility = View.GONE
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
@ -653,16 +797,16 @@ object UiUtil {
|
||||||
builder.setView(inputLayout)
|
builder.setView(inputLayout)
|
||||||
builder.setPositiveButton(
|
builder.setPositiveButton(
|
||||||
context.getString(R.string.ok)
|
context.getString(R.string.ok)
|
||||||
) { dialog: DialogInterface?, which: Int ->
|
) { _: DialogInterface?, _: Int ->
|
||||||
ok(editText.text.toString())
|
ok(editText.text.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle the negative button of the alert dialog
|
// handle the negative button of the alert dialog
|
||||||
builder.setNegativeButton(
|
builder.setNegativeButton(
|
||||||
context.getString(R.string.cancel)
|
context.getString(R.string.cancel)
|
||||||
) { dialog: DialogInterface?, which: Int -> }
|
) { _: DialogInterface?, _: Int -> }
|
||||||
|
|
||||||
builder.setNeutralButton("?") { dialog: DialogInterface?, which: Int ->
|
builder.setNeutralButton("?") { _: DialogInterface?, _: Int ->
|
||||||
val browserIntent =
|
val browserIntent =
|
||||||
Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/yt-dlp/yt-dlp#subtitle-options"))
|
Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/yt-dlp/yt-dlp#subtitle-options"))
|
||||||
context.startActivity(browserIntent)
|
context.startActivity(browserIntent)
|
||||||
|
|
@ -670,7 +814,7 @@ object UiUtil {
|
||||||
|
|
||||||
|
|
||||||
val dialog = builder.create()
|
val dialog = builder.create()
|
||||||
editText.doOnTextChanged { text, start, before, count ->
|
editText.doOnTextChanged { _, _, _, _ ->
|
||||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty()
|
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty()
|
||||||
}
|
}
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
@ -923,35 +1067,14 @@ object UiUtil {
|
||||||
|
|
||||||
val filenameTemplate = view.findViewById<Chip>(R.id.filename_template)
|
val filenameTemplate = view.findViewById<Chip>(R.id.filename_template)
|
||||||
filenameTemplate.setOnClickListener {
|
filenameTemplate.setOnClickListener {
|
||||||
val builder = MaterialAlertDialogBuilder(context)
|
val currentFilename = if (items.size == 1 || items.all { it.customFileNameTemplate == items[0].customFileNameTemplate }){
|
||||||
builder.setTitle(context.getString(R.string.file_name_template))
|
items[0].customFileNameTemplate
|
||||||
val inputLayout = context.layoutInflater.inflate(R.layout.textinput, null)
|
}else {
|
||||||
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 || items.all { it.customFileNameTemplate == items[0].customFileNameTemplate }){
|
|
||||||
editText.setText(items[0].customFileNameTemplate)
|
|
||||||
}
|
}
|
||||||
editText.setSelection(editText.text.length)
|
showFilenameTemplateDialog(context, currentFilename) {
|
||||||
builder.setView(inputLayout)
|
filenameTemplateSet(it)
|
||||||
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()
|
|
||||||
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_NEUTRAL).gravity = Gravity.START
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1022,35 +1145,14 @@ object UiUtil {
|
||||||
|
|
||||||
val filenameTemplate = view.findViewById<Chip>(R.id.filename_template)
|
val filenameTemplate = view.findViewById<Chip>(R.id.filename_template)
|
||||||
filenameTemplate.setOnClickListener {
|
filenameTemplate.setOnClickListener {
|
||||||
val builder = MaterialAlertDialogBuilder(context)
|
val currentFilename = if (items.size == 1 || items.all { it.customFileNameTemplate == items[0].customFileNameTemplate }){
|
||||||
builder.setTitle(context.getString(R.string.file_name_template))
|
items[0].customFileNameTemplate
|
||||||
val inputLayout = context.layoutInflater.inflate(R.layout.textinput, null)
|
}else {
|
||||||
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 || items.all { it.customFileNameTemplate == items[0].customFileNameTemplate }){
|
|
||||||
editText.setText(items[0].customFileNameTemplate)
|
|
||||||
}
|
}
|
||||||
editText.setSelection(editText.text.length)
|
showFilenameTemplateDialog(context, currentFilename) {
|
||||||
builder.setView(inputLayout)
|
filenameTemplateSet(it)
|
||||||
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()
|
|
||||||
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_NEUTRAL).gravity = Gravity.START
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val sponsorBlock = view.findViewById<Chip>(R.id.sponsorblock_filters)
|
val sponsorBlock = view.findViewById<Chip>(R.id.sponsorblock_filters)
|
||||||
|
|
@ -1222,9 +1324,6 @@ object UiUtil {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fun EditText.setTextAndRecalculateWidth(t : String){
|
fun EditText.setTextAndRecalculateWidth(t : String){
|
||||||
val scale = context.resources.displayMetrics.density
|
val scale = context.resources.displayMetrics.density
|
||||||
this.setText(t)
|
this.setText(t)
|
||||||
|
|
@ -1239,4 +1338,258 @@ object UiUtil {
|
||||||
}
|
}
|
||||||
this.requestLayout()
|
this.requestLayout()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun handleResultResponse(context: Activity, it: ResultViewModel.ResultsUiState){
|
||||||
|
val title = context.getString(it.errorMessage!!.first)
|
||||||
|
val message = it.errorMessage!!.second
|
||||||
|
|
||||||
|
val errDialog = MaterialAlertDialogBuilder(context)
|
||||||
|
.setTitle(title)
|
||||||
|
.setMessage(message)
|
||||||
|
|
||||||
|
for (a in it.actions!!){
|
||||||
|
when(a.second){
|
||||||
|
ResultViewModel.ResultAction.COPY_LOG -> {
|
||||||
|
errDialog.setPositiveButton(a.first) { d:DialogInterface?, _:Int ->
|
||||||
|
val clipboard: ClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
|
clipboard.setText(message)
|
||||||
|
d?.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
errDialog.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
fun handleDownloadsResponse(context: MainActivity, it: DownloadViewModel.DownloadsUiState, downloadViewModel: DownloadViewModel){
|
||||||
|
val downloadAnywayAction = it.actions?.first { it.second == DownloadViewModel.DownloadsAction.DOWNLOAD_ANYWAY}
|
||||||
|
if (downloadAnywayAction != null){
|
||||||
|
if (downloadAnywayAction.third == null) return
|
||||||
|
val downloads =downloadAnywayAction.third!!.toMutableList()
|
||||||
|
val ids = downloadAnywayAction.third!!.map { it.id }
|
||||||
|
val titles = downloadAnywayAction.third!!.map { it.title }
|
||||||
|
|
||||||
|
|
||||||
|
val title = context.getString(it.errorMessage!!.first)
|
||||||
|
val message = context.getString(it.errorMessage!!.second)
|
||||||
|
|
||||||
|
val errDialog = MaterialAlertDialogBuilder(context)
|
||||||
|
.setTitle(title)
|
||||||
|
.setMessage(message)
|
||||||
|
|
||||||
|
var dialog: Dialog? = null
|
||||||
|
|
||||||
|
val linearLayout = context.layoutInflater.inflate(R.layout.already_exists_card, null) as LinearLayout
|
||||||
|
ids.forEachIndexed { index, id ->
|
||||||
|
val alreadyExistsItem = context.layoutInflater.inflate(R.layout.already_exists_item, null)
|
||||||
|
alreadyExistsItem.tag = id.toString()
|
||||||
|
val ttle = alreadyExistsItem.findViewById<Button>(R.id.already_exists_title)
|
||||||
|
ttle.text = "${index + 1}. ${titles[index]}"
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
val editBtn = alreadyExistsItem.findViewById<Button>(R.id.already_exists_edit)
|
||||||
|
var downloadItem: DownloadItem = downloadViewModel.getItemByID(id)
|
||||||
|
val historyItem: HistoryItem? = downloadViewModel.getHistoryItemById(id)
|
||||||
|
|
||||||
|
if (historyItem != null){
|
||||||
|
val idx = downloads.indexOfFirst { it.id == historyItem.id }
|
||||||
|
downloadItem = downloadViewModel.createDownloadItemFromHistory(historyItem)
|
||||||
|
downloadItem.id = historyItem.id
|
||||||
|
downloads[idx] = downloadItem
|
||||||
|
}
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main){
|
||||||
|
editBtn.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
editBtn.setOnClickListener {
|
||||||
|
val resultItem = downloadViewModel.createResultItemFromDownload(downloadItem)
|
||||||
|
val onItemUpdated = object: ConfigureDownloadBottomSheetDialog.OnDownloadItemUpdateListener {
|
||||||
|
override fun onDownloadItemUpdate(
|
||||||
|
resultItemID: Long,
|
||||||
|
item: DownloadItem
|
||||||
|
) {
|
||||||
|
context.lifecycleScope.launch {
|
||||||
|
val idx = downloads.indexOfFirst { it.id == item.id }
|
||||||
|
downloads[idx] = item
|
||||||
|
withContext(Dispatchers.Main){
|
||||||
|
ttle.text = "${index + 1}. ${item.title}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val bottomSheet = ConfigureDownloadBottomSheetDialog(resultItem, downloadItem, onItemUpdated)
|
||||||
|
bottomSheet.show(context.supportFragmentManager, "configureDownloadSingleSheet")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (historyItem != null){
|
||||||
|
ttle.setOnClickListener {
|
||||||
|
showHistoryItemDetailsCard(historyItem, context, isPresent = true,
|
||||||
|
redownloadItem = { },
|
||||||
|
redownloadShowDownloadCard = {
|
||||||
|
val sheet = DownloadBottomSheetDialog(
|
||||||
|
result = downloadViewModel.createResultItemFromHistory(it),
|
||||||
|
type = it.type
|
||||||
|
)
|
||||||
|
sheet.show(context.supportFragmentManager, "downloadSingleSheet")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ttle.setOnLongClickListener {
|
||||||
|
showGenericDeleteDialog(context, ttle.text.toString(), accepted = {
|
||||||
|
linearLayout.removeView(alreadyExistsItem)
|
||||||
|
if (linearLayout.childCount == 0){
|
||||||
|
dialog?.dismiss()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
linearLayout.addView(alreadyExistsItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
errDialog.setView(linearLayout)
|
||||||
|
|
||||||
|
errDialog.setPositiveButton(downloadAnywayAction.first) { d:DialogInterface?, _:Int ->
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
linearLayout.allViews.forEach {view ->
|
||||||
|
val downloadItem = downloads.first { it.id == (view.tag as String).toLong()}
|
||||||
|
downloadItem.id = 0
|
||||||
|
downloadViewModel.queueDownloads(listOf(downloadItem), true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d?.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
errDialog.setNegativeButton(R.string.schedule) { d:DialogInterface?, _:Int ->
|
||||||
|
showDatePicker(context.supportFragmentManager) { calendar ->
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
val items = mutableListOf<DownloadItem>()
|
||||||
|
linearLayout.children.forEach {view ->
|
||||||
|
val downloadItem = downloads.first { it.id == (view.tag as String).toLong()}
|
||||||
|
downloadItem.downloadStartTime = calendar.timeInMillis
|
||||||
|
downloadItem.id = 0
|
||||||
|
items.add(downloadItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
runBlocking {
|
||||||
|
val chunks = items.chunked(10)
|
||||||
|
for (c in chunks) {
|
||||||
|
downloadViewModel.queueDownloads(c, true)
|
||||||
|
}
|
||||||
|
val first = items.first()
|
||||||
|
val date = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(first.downloadStartTime)
|
||||||
|
withContext(Dispatchers.Main){
|
||||||
|
Toast.makeText(context, context.getString(R.string.download_rescheduled_to) + " " + date, Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Main){
|
||||||
|
d?.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog = errDialog.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun showGenericDeleteDialog(context: Context, itemTitle: String, accepted: () -> Unit){
|
||||||
|
val deleteDialog = MaterialAlertDialogBuilder(context)
|
||||||
|
deleteDialog.setTitle(context.getString(R.string.you_are_going_to_delete) + " \"" + itemTitle + "\"!")
|
||||||
|
deleteDialog.setNegativeButton(context.getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||||
|
deleteDialog.setPositiveButton(context.getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||||
|
accepted()
|
||||||
|
}
|
||||||
|
deleteDialog.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showRemoveHistoryItemDialog(item: HistoryItem, context: Activity, delete: (item: HistoryItem, deleteFile: Boolean) -> Unit){
|
||||||
|
val deleteFile = booleanArrayOf(false)
|
||||||
|
val deleteDialog = MaterialAlertDialogBuilder(context)
|
||||||
|
deleteDialog.setTitle(context.getString(R.string.you_are_going_to_delete) + " \"" + item.title + "\"!")
|
||||||
|
val path = item.downloadPath
|
||||||
|
val file = File(path)
|
||||||
|
if (file.exists() && path.isNotEmpty()) {
|
||||||
|
deleteDialog.setMultiChoiceItems(
|
||||||
|
arrayOf(context.getString(R.string.delete_file_too)),
|
||||||
|
booleanArrayOf(false)
|
||||||
|
) { _: DialogInterface?, _: Int, b: Boolean -> deleteFile[0] = b }
|
||||||
|
}
|
||||||
|
deleteDialog.setNegativeButton(context.getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||||
|
deleteDialog.setPositiveButton(context.getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||||
|
delete(item, deleteFile[0])
|
||||||
|
}
|
||||||
|
deleteDialog.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showFilenameTemplateDialog(context: Activity, currentFilename: String, dialogTitle: String = context.getString(R.string.file_name_template), filenameSelected: (f: String) -> Unit){
|
||||||
|
val builder = MaterialAlertDialogBuilder(context)
|
||||||
|
builder.setTitle(dialogTitle)
|
||||||
|
val view = context.layoutInflater.inflate(R.layout.filename_template_dialog, null)
|
||||||
|
val editText = view.findViewById<EditText>(R.id.filename_edittext)
|
||||||
|
view.findViewById<TextInputLayout>(R.id.filename).hint = context.getString(R.string.file_name_template)
|
||||||
|
editText.setText(currentFilename)
|
||||||
|
editText.setSelection(editText.text.length)
|
||||||
|
builder.setView(view)
|
||||||
|
builder.setPositiveButton(
|
||||||
|
context.getString(R.string.ok)
|
||||||
|
) { _: DialogInterface?, _: Int ->
|
||||||
|
filenameSelected(editText.text.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle the negative button of the alert dialog
|
||||||
|
builder.setNegativeButton(
|
||||||
|
context.getString(R.string.cancel)
|
||||||
|
) { _: DialogInterface?, _: Int -> }
|
||||||
|
|
||||||
|
view.findViewById<View>(R.id.suggested).visibility = View.GONE
|
||||||
|
|
||||||
|
val dialog = builder.create()
|
||||||
|
dialog.show()
|
||||||
|
val imm = context.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
editText!!.postDelayed({
|
||||||
|
editText.requestFocus()
|
||||||
|
imm.showSoftInput(editText, 0)
|
||||||
|
}, 300)
|
||||||
|
|
||||||
|
//handle suggestion chips
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
val chipGroup = view.findViewById<ChipGroup>(R.id.filename_suggested_chipgroup)
|
||||||
|
val chips = mutableListOf<Chip>()
|
||||||
|
context.getStringArray(R.array.filename_templates).forEachIndexed { index, s ->
|
||||||
|
val tmp = context.layoutInflater.inflate(R.layout.filter_chip, chipGroup, false) as Chip
|
||||||
|
tmp.text = s.split("___")[0]
|
||||||
|
tmp.id = index
|
||||||
|
if (Build.VERSION.SDK_INT >= 26){
|
||||||
|
tmp.tooltipText = s.split("___")[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp.setOnClickListener {
|
||||||
|
val c = it as Chip
|
||||||
|
if(!c.isChecked){
|
||||||
|
editText.setText(editText.text.toString().replace(c.text.toString(), ""))
|
||||||
|
editText.setSelection(editText.text.length)
|
||||||
|
}else{
|
||||||
|
editText.append(c.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chips.add(tmp)
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Main){
|
||||||
|
view.findViewById<View>(R.id.suggested).visibility = View.VISIBLE
|
||||||
|
chips.forEach {
|
||||||
|
it.isChecked = editText.text.contains(it.text)
|
||||||
|
chipGroup!!.addView(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).gravity = Gravity.START
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -41,6 +41,7 @@ class UpdateUtil(var context: Context) {
|
||||||
Toast.LENGTH_LONG
|
Toast.LENGTH_LONG
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
|
val skippedVersions = sharedPreferences.getString("skip_updates", "")?.split(",")?.distinct()?.toMutableList() ?: mutableListOf()
|
||||||
val res = getGithubReleases()
|
val res = getGithubReleases()
|
||||||
|
|
||||||
if (res.isEmpty()){
|
if (res.isEmpty()){
|
||||||
|
|
@ -67,6 +68,8 @@ class UpdateUtil(var context: Context) {
|
||||||
isInLatest = false
|
isInLatest = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (skippedVersions.contains(v.tag_name)) isInLatest = true
|
||||||
|
|
||||||
if (isInLatest){
|
if (isInLatest){
|
||||||
result(context.getString(R.string.you_are_in_latest_version))
|
result(context.getString(R.string.you_are_in_latest_version))
|
||||||
return
|
return
|
||||||
|
|
@ -77,6 +80,11 @@ class UpdateUtil(var context: Context) {
|
||||||
.setTitle(v.tag_name)
|
.setTitle(v.tag_name)
|
||||||
.setMessage(v.body)
|
.setMessage(v.body)
|
||||||
.setIcon(R.drawable.ic_update_app)
|
.setIcon(R.drawable.ic_update_app)
|
||||||
|
.setNeutralButton(R.string.skip){ d: DialogInterface?, _:Int ->
|
||||||
|
skippedVersions.add(v.tag_name)
|
||||||
|
sharedPreferences.edit().putString("skip_updates", skippedVersions.joinToString(",")).apply()
|
||||||
|
d?.dismiss()
|
||||||
|
}
|
||||||
.setNegativeButton(context.resources.getString(R.string.cancel)) { _: DialogInterface?, _: Int -> }
|
.setNegativeButton(context.resources.getString(R.string.cancel)) { _: DialogInterface?, _: Int -> }
|
||||||
.setPositiveButton(context.resources.getString(R.string.update)) { _: DialogInterface?, _: Int ->
|
.setPositiveButton(context.resources.getString(R.string.update)) { _: DialogInterface?, _: Int ->
|
||||||
runCatching {
|
runCatching {
|
||||||
|
|
|
||||||
|
|
@ -136,13 +136,10 @@ class DownloadWorker(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
if (logDownloads){
|
runBlocking {
|
||||||
runBlocking {
|
logItem.id = logRepo.insert(logItem)
|
||||||
logItem.id = logRepo.insert(logItem)
|
downloadItem.logID = logItem.id
|
||||||
downloadItem.logID = logItem.id
|
dao.update(downloadItem)
|
||||||
dao.update(downloadItem)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val noCache = !sharedPreferences.getBoolean("cache_downloads", true) && File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite()
|
val noCache = !sharedPreferences.getBoolean("cache_downloads", true) && File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite()
|
||||||
|
|
@ -156,10 +153,8 @@ class DownloadWorker(
|
||||||
line, progress.toInt(), 0, title,
|
line, progress.toInt(), 0, title,
|
||||||
NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID
|
NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID
|
||||||
)
|
)
|
||||||
if (logDownloads){
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
logRepo.update(line, logItem.id)
|
||||||
logRepo.update(line, logItem.id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.onSuccess {
|
}.onSuccess {
|
||||||
|
|
@ -169,7 +164,7 @@ class DownloadWorker(
|
||||||
|
|
||||||
if (noCache){
|
if (noCache){
|
||||||
setProgressAsync(workDataOf("progress" to 100, "output" to "Scanning Files", "id" to downloadItem.id))
|
setProgressAsync(workDataOf("progress" to 100, "output" to "Scanning Files", "id" to downloadItem.id))
|
||||||
val p = it.out.split("\r")
|
val p = it.out.split("\n")
|
||||||
.filter { it.startsWith("'/storage") }
|
.filter { it.startsWith("'/storage") }
|
||||||
.map { it.removePrefix("'") }
|
.map { it.removePrefix("'") }
|
||||||
.map { it.removeSuffix("'\n") }
|
.map { it.removeSuffix("'\n") }
|
||||||
|
|
@ -241,6 +236,8 @@ class DownloadWorker(
|
||||||
|
|
||||||
if (logDownloads){
|
if (logDownloads){
|
||||||
logRepo.update(it.out, logItem.id)
|
logRepo.update(it.out, logItem.id)
|
||||||
|
}else{
|
||||||
|
logRepo.delete(logItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
9
app/src/main/res/layout/already_exists_card.xml
Normal file
9
app/src/main/res/layout/already_exists_card.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout android:id="@+id/existing_list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
43
app/src/main/res/layout/already_exists_item.xml
Normal file
43
app/src/main/res/layout/already_exists_item.xml
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?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"
|
||||||
|
android:id="@+id/playlist_card_constraintLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_marginBottom="10dp"
|
||||||
|
android:checkable="true"
|
||||||
|
android:paddingHorizontal="20dp"
|
||||||
|
android:paddingVertical="5dp"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/already_exists_title"
|
||||||
|
style="@style/Widget.Material3.Button.TonalButton.Icon"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:autoLink="all"
|
||||||
|
android:gravity="start"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
android:layout_marginEnd="10dp"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/already_exists_edit"
|
||||||
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/already_exists_edit"
|
||||||
|
style="@style/Widget.Material3.Button.IconButton.Filled"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:icon="@drawable/ic_edit"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/already_exists_title"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/already_exists_title" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
68
app/src/main/res/layout/command_templates_sort_sheet.xml
Normal file
68
app/src/main/res/layout/command_templates_sort_sheet.xml
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout style="@style/Widget.Material3.BottomSheet"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:text="@string/sort_by"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_margin="20dp"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/date"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:paddingVertical="10dp"
|
||||||
|
android:paddingHorizontal="20dp"
|
||||||
|
android:drawablePadding="30dp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="15sp"
|
||||||
|
android:text="@string/date_added"
|
||||||
|
app:drawableStartCompat="@drawable/ic_arrow_up" />
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:paddingVertical="10dp"
|
||||||
|
android:paddingHorizontal="20dp"
|
||||||
|
android:drawablePadding="30dp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="15sp"
|
||||||
|
android:text="@string/title"
|
||||||
|
app:drawableStartCompat="@drawable/ic_arrow_up" />
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/length"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:paddingVertical="10dp"
|
||||||
|
android:paddingHorizontal="20dp"
|
||||||
|
android:drawablePadding="30dp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="15sp"
|
||||||
|
android:text="@string/length"
|
||||||
|
app:drawableStartCompat="@drawable/ic_arrow_up" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
61
app/src/main/res/layout/filename_template_dialog.xml
Normal file
61
app/src/main/res/layout/filename_template_dialog.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"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/filename"
|
||||||
|
style="@style/Widget.Material3.TextInputLayout.FilledBox"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="10dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/filename_edittext"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="text" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/suggested"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/filename"
|
||||||
|
android:text="@string/suggested" />
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintHeight_max="200dp"
|
||||||
|
android:paddingHorizontal="10dp"
|
||||||
|
app:layout_constraintVertical_bias="0.0"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/suggested">
|
||||||
|
|
||||||
|
<com.google.android.material.chip.ChipGroup
|
||||||
|
android:id="@+id/filename_suggested_chipgroup"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:chipSpacingVertical="-7dp"
|
||||||
|
app:chipSpacingHorizontal="5dp"
|
||||||
|
app:selectionRequired="false"
|
||||||
|
app:singleSelection="false">
|
||||||
|
|
||||||
|
</com.google.android.material.chip.ChipGroup>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
@ -34,6 +34,22 @@
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
|
||||||
|
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
|
android:id="@+id/format_loading_progress"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:indeterminate="true"
|
||||||
|
app:trackColor="#000"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:alpha="0.3"
|
||||||
|
android:scaleY="200"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/container"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/container"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/container"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/container"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,7 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingHorizontal="20dp"
|
android:paddingHorizontal="20dp"
|
||||||
|
android:paddingVertical="5dp"
|
||||||
android:text="@string/video" />
|
android:text="@string/video" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
|
@ -111,6 +112,7 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingHorizontal="20dp"
|
android:paddingHorizontal="20dp"
|
||||||
|
android:paddingVertical="5dp"
|
||||||
android:text="@string/audio" />
|
android:text="@string/audio" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
|
|
||||||
|
|
@ -22,66 +22,74 @@
|
||||||
app:navigationIcon="@drawable/ic_back"
|
app:navigationIcon="@drawable/ic_back"
|
||||||
android:layout_height="match_parent"/>
|
android:layout_height="match_parent"/>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_scrollFlags="scroll|enterAlways"
|
||||||
|
android:layout_margin="10dp">
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
<HorizontalScrollView
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical"
|
|
||||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
|
|
||||||
|
|
||||||
|
|
||||||
<HorizontalScrollView
|
|
||||||
android:id="@+id/chips_command"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:scrollbars="none"
|
|
||||||
android:layout_margin="10dp"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<com.google.android.material.chip.ChipGroup
|
<LinearLayout
|
||||||
|
android:id="@+id/history_selection_chips"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
app:selectionRequired="false"
|
|
||||||
app:singleSelection="false">
|
|
||||||
|
|
||||||
<com.google.android.material.chip.Chip
|
<com.google.android.material.chip.Chip
|
||||||
android:id="@+id/newTemplate"
|
android:id="@+id/sortChip"
|
||||||
style="@style/Widget.Material3.Chip.Assist"
|
style="@style/Widget.Material3.Chip.Assist"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/new_template"
|
android:text="@string/sort_by"
|
||||||
app:chipIcon="@drawable/ic_plus" />
|
app:chipIcon="@drawable/ic_down" />
|
||||||
|
|
||||||
<com.google.android.material.chip.Chip
|
<View style="@style/Divider.Vertical"/>
|
||||||
android:id="@+id/shortcuts"
|
|
||||||
style="@style/Widget.Material3.Chip.Assist"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/shortcuts"
|
|
||||||
app:chipIcon="@drawable/ic_shortcut" />
|
|
||||||
|
|
||||||
</com.google.android.material.chip.ChipGroup>
|
<com.google.android.material.chip.ChipGroup
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
app:selectionRequired="false"
|
||||||
|
app:singleSelection="true"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
</LinearLayout>
|
<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" />
|
||||||
|
|
||||||
</HorizontalScrollView>
|
<com.google.android.material.chip.Chip
|
||||||
|
android:id="@+id/shortcuts"
|
||||||
|
style="@style/Widget.Material3.Chip.Assist"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/shortcuts"
|
||||||
|
app:chipIcon="@drawable/ic_shortcut" />
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
</com.google.android.material.chip.ChipGroup>
|
||||||
android:layout_below="@+id/chips_command"
|
|
||||||
android:id="@+id/template_recyclerview"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
>
|
|
||||||
|
|
||||||
</androidx.recyclerview.widget.RecyclerView>
|
</LinearLayout>
|
||||||
|
|
||||||
</RelativeLayout>
|
</HorizontalScrollView>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||||
|
android:id="@+id/template_recyclerview"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
>
|
||||||
|
|
||||||
|
</androidx.recyclerview.widget.RecyclerView>
|
||||||
|
|
||||||
|
|
||||||
<include layout="@layout/history_no_results"
|
<include layout="@layout/history_no_results"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout style="@style/Widget.Material3.BottomSheet"
|
<FrameLayout xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
style="@style/Widget.Material3.BottomSheet"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
|
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
|
||||||
|
|
@ -17,10 +18,11 @@
|
||||||
android:padding="20dp">
|
android:padding="20dp">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/linearLayout2"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/downloads_download_button_layout"
|
app:layout_constraintEnd_toStartOf="@+id/download_button_type"
|
||||||
app:layout_constraintHorizontal_bias="0.0"
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
@ -43,28 +45,19 @@
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
android:id="@+id/downloads_download_button_layout"
|
android:id="@+id/download_button_type"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:layout_marginHorizontal="10dp"
|
||||||
|
android:clickable="false"
|
||||||
|
android:elevation="0dp"
|
||||||
|
app:borderWidth="0dp"
|
||||||
|
app:cornerRadius="10dp"
|
||||||
|
app:elevation="0dp"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
android:contentDescription="@string/share" />
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/downloads_download_button_type"
|
|
||||||
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
|
|
||||||
android:layout_width="55dp"
|
|
||||||
android:layout_height="55dp"
|
|
||||||
android:layout_margin="10dp"
|
|
||||||
android:layout_marginBottom="10dp"
|
|
||||||
android:clickable="false"
|
|
||||||
android:elevation="0dp"
|
|
||||||
app:borderWidth="0dp"
|
|
||||||
app:cornerRadius="10dp"
|
|
||||||
app:elevation="0dp" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,59 +1,68 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:app="http://schemas.android.com/apk/res-auto"
|
<androidx.constraintlayout.widget.ConstraintLayout 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"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
android:id="@+id/webview_appbarlayout"
|
android:id="@+id/coordinatorLayout2"
|
||||||
app:liftOnScroll="true"
|
|
||||||
android:background="@android:color/transparent"
|
|
||||||
android:elevation="0dp"
|
|
||||||
app:elevation="0dp"
|
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:id="@+id/webviewToolbar"
|
android:id="@+id/webview_appbarlayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="?attr/actionBarSize"
|
android:layout_height="wrap_content"
|
||||||
app:layout_scrollFlags="scroll"
|
android:background="@android:color/transparent"
|
||||||
app:navigationIcon="?attr/homeAsUpIndicator"
|
android:elevation="0dp"
|
||||||
>
|
android:fitsSystemWindows="true"
|
||||||
|
app:elevation="0dp"
|
||||||
|
app:liftOnScroll="true">
|
||||||
|
|
||||||
<Button
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
android:id="@+id/generate"
|
android:id="@+id/webviewToolbar"
|
||||||
android:layout_gravity="end"
|
android:layout_width="match_parent"
|
||||||
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
|
android:layout_height="?attr/actionBarSize"
|
||||||
android:layout_width="wrap_content"
|
app:layout_scrollFlags="scroll"
|
||||||
android:layout_marginHorizontal="10dp"
|
app:navigationIcon="?attr/homeAsUpIndicator">
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:autoLink="all"
|
|
||||||
android:text="@string/ok"
|
|
||||||
app:icon="@drawable/ic_check"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
tools:ignore="PrivateResource" />
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.MaterialToolbar>
|
<Button
|
||||||
|
android:id="@+id/generate"
|
||||||
|
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
|
android:autoLink="all"
|
||||||
|
android:text="@string/ok"
|
||||||
|
app:icon="@drawable/ic_check"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:ignore="PrivateResource" />
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.MaterialToolbar>
|
||||||
|
|
||||||
<RelativeLayout
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
android:id="@+id/rl"
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<androidx.compose.ui.platform.ComposeView
|
||||||
|
android:id="@+id/webview_compose"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
|
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
|
||||||
android:layout_height="match_parent">
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/coordinatorLayout2"
|
||||||
|
tools:composableName="com.deniscerri.ytdlnis.ui.more.WebViewActivity.WebViewView" />
|
||||||
|
|
||||||
<androidx.compose.ui.platform.ComposeView
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
android:id="@+id/webview_compose"
|
|
||||||
tools:composableName="com.deniscerri.ytdlnis.ui.more.WebViewActivity.WebViewView"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_width="match_parent"/>
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,14 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/search_command"
|
||||||
|
android:title="@string/search"
|
||||||
|
android:icon="@drawable/ic_search"
|
||||||
|
app:showAsAction="collapseActionView|ifRoom"
|
||||||
|
app:actionViewClass="androidx.appcompat.widget.SearchView"/>
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/import_clipboard"
|
android:id="@+id/import_clipboard"
|
||||||
android:title="@string/import_from_clipboard"
|
android:title="@string/import_from_clipboard"
|
||||||
|
|
|
||||||
|
|
@ -100,13 +100,7 @@
|
||||||
android:id="@+id/resultCardDetailsDialog"
|
android:id="@+id/resultCardDetailsDialog"
|
||||||
android:name="com.deniscerri.ytdlnis.ui.downloadcard.ResultCardDetailsDialog"
|
android:name="com.deniscerri.ytdlnis.ui.downloadcard.ResultCardDetailsDialog"
|
||||||
android:label="ResultCardDetailsDialog" />
|
android:label="ResultCardDetailsDialog" />
|
||||||
<navigation android:id="@+id/settingsNav"
|
<navigation android:id="@+id/settingsNav">
|
||||||
app:startDestination="@id/settingsFrame">
|
|
||||||
<fragment
|
|
||||||
android:id="@+id/settingsFrame"
|
|
||||||
android:name="com.deniscerri.ytdlnis.ui.more.settings.SettingsFragmentFrame"
|
|
||||||
android:label="settings_frame"
|
|
||||||
tools:layout="@layout/fragment_settings" />
|
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/folderSettingsFragment"
|
android:id="@+id/folderSettingsFragment"
|
||||||
android:name="com.deniscerri.ytdlnis.ui.more.settings.FolderSettingsFragment"
|
android:name="com.deniscerri.ytdlnis.ui.more.settings.FolderSettingsFragment"
|
||||||
|
|
|
||||||
|
|
@ -821,4 +821,111 @@
|
||||||
<item>m4a|mp4a|aac</item>
|
<item>m4a|mp4a|aac</item>
|
||||||
<item>opus</item>
|
<item>opus</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="filename_templates">
|
||||||
|
<item>%(uploader)s___(string): Full name of the video uploader)</item>
|
||||||
|
<item>%(title)s___(string): Video title)</item>
|
||||||
|
<item>%(id)s___(string): Video identifier)</item>
|
||||||
|
<item>%(fulltitle)s___(string): Video title ignoring live timestamp and generic title)</item>
|
||||||
|
<item>%(ext)s___(string): Video filename extension)</item>
|
||||||
|
<item>%(alt_title)s___(string): A secondary title of the video)</item>
|
||||||
|
<item>%(description)s___(string): The description of the video)</item>
|
||||||
|
<item>%(display_id)s___(string): An alternative identifier for the video)</item>
|
||||||
|
<item>%(license)s___(string): License name the video is licensed under)</item>
|
||||||
|
<item>%(creator)s___(string): The creator of the video)</item>
|
||||||
|
<item>%(timestamp)s___(numeric): UNIX timestamp of the moment the video became available)</item>
|
||||||
|
<item>%(upload_date)s___(string): Video upload date in UTC (YYYYMMDD))</item>
|
||||||
|
<item>%(release_timestamp)s___(numeric): UNIX timestamp of the moment the video was released)</item>
|
||||||
|
<item>%(release_date)s___(string): The date (YYYYMMDD) when the video was released in UTC)</item>
|
||||||
|
<item>%(modified_timestamp)s___(numeric): UNIX timestamp of the moment the video was last modified)</item>
|
||||||
|
<item>%(modified_date)s___(string): The date (YYYYMMDD) when the video was last modified in UTC)</item>
|
||||||
|
<item>%(uploader_id)s___(string): Nickname or id of the video uploader)</item>
|
||||||
|
<item>%(channel)s___(string): Full name of the channel the video is uploaded on)</item>
|
||||||
|
<item>%(channel_id)s___(string): Id of the channel)</item>
|
||||||
|
<item>%(channel_follower_count)s___(numeric): Number of followers of the channel)</item>
|
||||||
|
<item>%(channel_is_verified)s___(boolean): Whether the channel is verified on the platform)</item>
|
||||||
|
<item>%(location)s___(string): Physical location where the video was filmed)</item>
|
||||||
|
<item>%(duration)s___(numeric): Length of the video in seconds)</item>
|
||||||
|
<item>%(duration_string)s___(string): Length of the video (HH:mm:ss))</item>
|
||||||
|
<item>%(view_count)s___(numeric): How many users have watched the video on the platform)</item>
|
||||||
|
<item>%(concurrent_view_count)s___(numeric): How many users are currently watching the video on the platform.)</item>
|
||||||
|
<item>%(like_count)s___(numeric): Number of positive ratings of the video)</item>
|
||||||
|
<item>%(dislike_count)s___(numeric): Number of negative ratings of the video)</item>
|
||||||
|
<item>%(repost_count)s___(numeric): Number of reposts of the video)</item>
|
||||||
|
<item>%(average_rating)s___(numeric): Average rating give by users, the scale used depends on the webpage)</item>
|
||||||
|
<item>%(comment_count)s___(numeric): Number of comments on the video (For some extractors, comments are only downloaded at the end, and so this field cannot be used))</item>
|
||||||
|
<item>%(age_limit)s___(numeric): Age restriction for the video (years))</item>
|
||||||
|
<item>%(live_status)s___(string): One of "not_live", "is_live", "is_upcoming", "was_live", "post_live" (was live, but VOD is not yet processed))</item>
|
||||||
|
<item>%(is_live)s___(boolean): Whether this video is a live stream or a fixed-length video)</item>
|
||||||
|
<item>%(was_live)s___(boolean): Whether this video was originally a live stream)</item>
|
||||||
|
<item>%(playable_in_embed)s___(string): Whether this video is allowed to play in embedded players on other sites)</item>
|
||||||
|
<item>%(availability)s___(string): Whether the video is "private", "premium_only", "subscriber_only", "needs_auth", "unlisted" or "public")</item>
|
||||||
|
<item>%(start_time)s___(numeric): Time in seconds where the reproduction should start, as specified in the URL)</item>
|
||||||
|
<item>%(end_time)s___(numeric): Time in seconds where the reproduction should end, as specified in the URL)</item>
|
||||||
|
<item>%(extractor)s___(string): Name of the extractor)</item>
|
||||||
|
<item>%(extractor_key)s___(string): Key name of the extractor)</item>
|
||||||
|
<item>%(epoch)s___(numeric): Unix epoch of when the information extraction was completed)</item>
|
||||||
|
<item>%(autonumber)s___(numeric): Number that will be increased with each download, starting at --autonumber-start, padded with leading zeros to 5 digits)</item>
|
||||||
|
<item>%(video_autonumber)s___(numeric): Number that will be increased with each video)</item>
|
||||||
|
<item>%(n_entries)s___(numeric): Total number of extracted items in the playlist)</item>
|
||||||
|
<item>%(playlist_id)s___(string): Identifier of the playlist that contains the video)</item>
|
||||||
|
<item>%(playlist_title)s___(string): Name of the playlist that contains the video)</item>
|
||||||
|
<item>%(playlist)s___(string): playlist_id or playlist_title)</item>
|
||||||
|
<item>%(playlist_count)s___(numeric): Total number of items in the playlist. May not be known if entire playlist is not extracted)</item>
|
||||||
|
<item>%(playlist_index)s___(numeric): Index of the video in the playlist padded with leading zeros according the final index)</item>
|
||||||
|
<item>%(playlist_autonumber)s___(numeric): Position of the video in the playlist download queue padded with leading zeros according to the total length of the playlist)</item>
|
||||||
|
<item>%(playlist_uploader)s___(string): Full name of the playlist uploader)</item>
|
||||||
|
<item>%(playlist_uploader_id)s___(string): Nickname or id of the playlist uploader)</item>
|
||||||
|
<item>%(webpage_url)s___(string): A URL to the video webpage which if given to yt-dlp should allow to get the same result again)</item>
|
||||||
|
<item>%(webpage_url_basename)s___(string): The basename of the webpage URL)</item>
|
||||||
|
<item>%(webpage_url_domain)s___(string): The domain of the webpage URL)</item>
|
||||||
|
<item>%(original_url)s___(string): The URL given by the user (or same as webpage_url for playlist entries))</item>
|
||||||
|
<item>%(chapter)s___(string): Name or title of the chapter the video belongs to)</item>
|
||||||
|
<item>%(chapter_number)s___(numeric): Number of the chapter the video belongs to)</item>
|
||||||
|
<item>%(chapter_id)s___(string): Id of the chapter the video belongs to)</item>
|
||||||
|
<item>%(series)s___(string): Title of the series or programme the video episode belongs to)</item>
|
||||||
|
<item>%(season)s___(string): Title of the season the video episode belongs to)</item>
|
||||||
|
<item>%(season_number)s___(numeric): Number of the season the video episode belongs to)</item>
|
||||||
|
<item>%(season_id)s___(string): Id of the season the video episode belongs to)</item>
|
||||||
|
<item>%(episode)s___(string): Title of the video episode)</item>
|
||||||
|
<item>%(episode_number)s___(numeric): Number of the video episode within a season)</item>
|
||||||
|
<item>%(episode_id)s___(string): Id of the video episode)</item>
|
||||||
|
<item>%(track)s___(string): Title of the track)</item>
|
||||||
|
<item>%(track_number)s___(numeric): Number of the track within an album or a disc)</item>
|
||||||
|
<item>%(track_id)s___(string): Id of the track)</item>
|
||||||
|
<item>%(artist)s___(string): Artist(s) of the track)</item>
|
||||||
|
<item>%(genre)s___(string): Genre(s) of the track)</item>
|
||||||
|
<item>%(album)s___(string): Title of the album the track belongs to)</item>
|
||||||
|
<item>%(album_type)s___(string): Type of the album)</item>
|
||||||
|
<item>%(album_artist)s___(string): List of all artists appeared on the album)</item>
|
||||||
|
<item>%(disc_number)s___(numeric): Number of the disc or other physical medium the track belongs to)</item>
|
||||||
|
<item>%(release_year)s___(numeric): Year (YYYY) when the album was released)</item>
|
||||||
|
<item>%(section_title)s___(string): Title of the chapter)</item>
|
||||||
|
<item>%(section_number)s___(numeric): Number of the chapter within the file)</item>
|
||||||
|
<item>%(section_start)s___(numeric): Start time of the chapter in seconds)</item>
|
||||||
|
<item>%(section_end)s___(numeric): End time of the chapter in seconds)</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="modify_download_card">
|
||||||
|
<item>@string/schedule</item>
|
||||||
|
<item>@string/title</item>
|
||||||
|
<item>@string/author</item>
|
||||||
|
<item>@string/container</item>
|
||||||
|
<item>@string/adjust_audio</item>
|
||||||
|
<item>@string/adjust_video</item>
|
||||||
|
<item>@string/adjust_templates</item>
|
||||||
|
<item>@string/url</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="modify_download_card_values">
|
||||||
|
<item>schedule</item>
|
||||||
|
<item>title</item>
|
||||||
|
<item>author</item>
|
||||||
|
<item>container</item>
|
||||||
|
<item>adjust_audio</item>
|
||||||
|
<item>adjust_video</item>
|
||||||
|
<item>adjust_templates</item>
|
||||||
|
<item>url</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -30,6 +30,7 @@
|
||||||
<string name="confirm_delete_history_desc">Delete the entire history, permanently</string>
|
<string name="confirm_delete_history_desc">Delete the entire history, permanently</string>
|
||||||
<string name="confirm_delete_history">Confirm</string>
|
<string name="confirm_delete_history">Confirm</string>
|
||||||
<string name="search_history_hint">Search from History</string>
|
<string name="search_history_hint">Search from History</string>
|
||||||
|
<string name="search_command_hint">Search from Commands</string>
|
||||||
<string name="no_results">No Results</string>
|
<string name="no_results">No Results</string>
|
||||||
<string name="more">More</string>
|
<string name="more">More</string>
|
||||||
<string name="update_app">Check for updates</string>
|
<string name="update_app">Check for updates</string>
|
||||||
|
|
@ -310,4 +311,11 @@
|
||||||
<string name="remember_download_type">Remember Download Type</string>
|
<string name="remember_download_type">Remember Download Type</string>
|
||||||
<string name="remember_download_type_summary">Select the last opened download type for the current item</string>
|
<string name="remember_download_type_summary">Select the last opened download type for the current item</string>
|
||||||
<string name="user_agent_header">User-Agent header</string>
|
<string name="user_agent_header">User-Agent header</string>
|
||||||
|
<string name="length">Length</string>
|
||||||
|
<string name="security">Security</string>
|
||||||
|
<string name="license">License</string>
|
||||||
|
<string name="download_already_exists_summary">Do you still want to download?</string>
|
||||||
|
<string name="schedule">Schedule</string>
|
||||||
|
<string name="skip">Skip</string>
|
||||||
|
<string name="modify_download_card">Modify Download Card</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -37,6 +37,13 @@
|
||||||
app:title="@string/download_over_metered_networks" />
|
app:title="@string/download_over_metered_networks" />
|
||||||
|
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:widgetLayout="@layout/preferece_material_switch"
|
||||||
|
app:defaultValue="false"
|
||||||
|
android:icon="@drawable/baseline_cookie_24"
|
||||||
|
android:key="use_cookies"
|
||||||
|
app:title="@string/use_cookies" />
|
||||||
|
|
||||||
<EditTextPreference
|
<EditTextPreference
|
||||||
android:icon="@drawable/baseline_network_locked_24"
|
android:icon="@drawable/baseline_network_locked_24"
|
||||||
app:key="proxy"
|
app:key="proxy"
|
||||||
|
|
@ -111,10 +118,14 @@
|
||||||
|
|
||||||
<EditTextPreference
|
<EditTextPreference
|
||||||
app:key="retries"
|
app:key="retries"
|
||||||
|
android:defaultValue=""
|
||||||
|
app:useSimpleSummaryProvider="true"
|
||||||
app:title="@string/retries" />
|
app:title="@string/retries" />
|
||||||
|
|
||||||
<EditTextPreference
|
<EditTextPreference
|
||||||
app:key="fragment_retries"
|
app:key="fragment_retries"
|
||||||
|
android:defaultValue=""
|
||||||
|
app:useSimpleSummaryProvider="true"
|
||||||
app:title="@string/fragment_retries" />
|
app:title="@string/fragment_retries" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,19 @@
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
|
android:defaultValue=""
|
||||||
app:icon="@drawable/ic_music_downloaded"
|
app:icon="@drawable/ic_music_downloaded"
|
||||||
app:key="music_path"
|
app:key="music_path"
|
||||||
app:title="@string/music_directory" />
|
app:title="@string/music_directory" />
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
|
android:defaultValue=""
|
||||||
app:icon="@drawable/ic_video_downloaded"
|
app:icon="@drawable/ic_video_downloaded"
|
||||||
app:key="video_path"
|
app:key="video_path"
|
||||||
app:title="@string/video_directory" />
|
app:title="@string/video_directory" />
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
|
android:defaultValue=""
|
||||||
app:icon="@drawable/ic_terminal"
|
app:icon="@drawable/ic_terminal"
|
||||||
app:key="command_path"
|
app:key="command_path"
|
||||||
app:title="@string/command_directory" />
|
app:title="@string/command_directory" />
|
||||||
|
|
@ -43,14 +46,14 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<EditTextPreference
|
<Preference
|
||||||
android:icon="@drawable/ic_textformat"
|
android:icon="@drawable/ic_textformat"
|
||||||
app:key="file_name_template"
|
app:key="file_name_template"
|
||||||
app:useSimpleSummaryProvider="true"
|
app:useSimpleSummaryProvider="true"
|
||||||
app:defaultValue="%(uploader)s - %(title)s"
|
app:defaultValue="%(uploader)s - %(title)s"
|
||||||
app:title="@string/file_name_template" />
|
app:title="@string/file_name_template" />
|
||||||
|
|
||||||
<EditTextPreference
|
<Preference
|
||||||
android:icon="@drawable/ic_textformat"
|
android:icon="@drawable/ic_textformat"
|
||||||
app:key="file_name_template_audio"
|
app:key="file_name_template_audio"
|
||||||
app:useSimpleSummaryProvider="true"
|
app:useSimpleSummaryProvider="true"
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,15 @@
|
||||||
app:key="hide_thumbnails"
|
app:key="hide_thumbnails"
|
||||||
app:title="@string/hide_thumbnails" />
|
app:title="@string/hide_thumbnails" />
|
||||||
|
|
||||||
|
<MultiSelectListPreference
|
||||||
|
app:icon="@drawable/ic_card"
|
||||||
|
android:dialogTitle="@string/modify_download_card"
|
||||||
|
android:entries="@array/modify_download_card"
|
||||||
|
android:entryValues="@array/modify_download_card_values"
|
||||||
|
android:defaultValue="@array/modify_download_card_values"
|
||||||
|
app:key="modify_download_card"
|
||||||
|
app:title="@string/modify_download_card" />
|
||||||
|
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@
|
||||||
app:title="@string/video_format" />
|
app:title="@string/video_format" />
|
||||||
|
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:defaultValue=""
|
android:defaultValue="avc|h264"
|
||||||
android:entries="@array/video_codec"
|
android:entries="@array/video_codec"
|
||||||
android:entryValues="@array/video_codec_values"
|
android:entryValues="@array/video_codec_values"
|
||||||
android:icon="@drawable/ic_code"
|
android:icon="@drawable/ic_code"
|
||||||
|
|
@ -157,7 +157,7 @@
|
||||||
app:title="@string/preferred_video_codec" />
|
app:title="@string/preferred_video_codec" />
|
||||||
|
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:defaultValue=""
|
android:defaultValue="m4a|mp4a|aac"
|
||||||
android:entries="@array/audio_codec"
|
android:entries="@array/audio_codec"
|
||||||
android:entryValues="@array/audio_codec_values"
|
android:entryValues="@array/audio_codec_values"
|
||||||
android:icon="@drawable/ic_code"
|
android:icon="@drawable/ic_code"
|
||||||
|
|
@ -211,9 +211,4 @@
|
||||||
android:summary="@string/use_extra_commands_summary"
|
android:summary="@string/use_extra_commands_summary"
|
||||||
app:title="@string/use_extra_commands" />
|
app:title="@string/use_extra_commands" />
|
||||||
|
|
||||||
<Preference
|
|
||||||
app:isPreferenceVisible="false"
|
|
||||||
android:key="use_cookies"
|
|
||||||
android:defaultValue="false" />
|
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|
@ -73,7 +73,7 @@
|
||||||
app:icon="@drawable/baseline_security_24"
|
app:icon="@drawable/baseline_security_24"
|
||||||
app:key="security"
|
app:key="security"
|
||||||
app:summary="🔐 YTDLnis - Privacy Policy"
|
app:summary="🔐 YTDLnis - Privacy Policy"
|
||||||
app:title="SECURITY">
|
app:title="@string/security">
|
||||||
<intent
|
<intent
|
||||||
android:action="android.intent.action.VIEW"
|
android:action="android.intent.action.VIEW"
|
||||||
android:data="https://github.com/deniscerri/ytdlnis/blob/main/SECURITY.md" />
|
android:data="https://github.com/deniscerri/ytdlnis/blob/main/SECURITY.md" />
|
||||||
|
|
@ -83,7 +83,7 @@
|
||||||
app:icon="@drawable/baseline_draw_24"
|
app:icon="@drawable/baseline_draw_24"
|
||||||
app:key="license"
|
app:key="license"
|
||||||
app:summary="GNU General Public License v3.0"
|
app:summary="GNU General Public License v3.0"
|
||||||
app:title="LICENSE">
|
app:title="@string/license">
|
||||||
<intent
|
<intent
|
||||||
android:action="android.intent.action.VIEW"
|
android:action="android.intent.action.VIEW"
|
||||||
android:data="https://github.com/deniscerri/ytdlnis/blob/main/LICENSE" />
|
android:data="https://github.com/deniscerri/ytdlnis/blob/main/LICENSE" />
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,11 @@
|
||||||
app:summary="@string/update_app_summary"
|
app:summary="@string/update_app_summary"
|
||||||
app:title="@string/update_app" />
|
app:title="@string/update_app" />
|
||||||
|
|
||||||
|
<EditTextPreference
|
||||||
|
app:isPreferenceVisible="false"
|
||||||
|
app:key="skip_updates"
|
||||||
|
app:defaultValue="" />
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
android:widgetLayout="@layout/preferece_material_switch"
|
android:widgetLayout="@layout/preferece_material_switch"
|
||||||
app:defaultValue="false"
|
app:defaultValue="false"
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ buildscript {
|
||||||
coroutineVer = '1.7.3'
|
coroutineVer = '1.7.3'
|
||||||
retrofitVer = "2.9.0"
|
retrofitVer = "2.9.0"
|
||||||
kodeinVer = "7.16.0"
|
kodeinVer = "7.16.0"
|
||||||
navVer = "2.6.0"
|
navVer = "2.7.4"
|
||||||
media3_version = "1.1.1"
|
media3_version = "1.1.1"
|
||||||
agp_version = '8.1.0'
|
agp_version = '8.1.0'
|
||||||
agp_version1 = '7.4.2'
|
agp_version1 = '7.4.2'
|
||||||
|
|
@ -34,11 +34,11 @@ buildscript {
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application' version '7.4.2' apply false
|
id 'com.android.application' version '8.1.1' apply false
|
||||||
id 'com.android.library' version '7.4.2' apply false
|
id 'com.android.library' version '8.1.1' apply false
|
||||||
id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
|
id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
|
||||||
id "org.jetbrains.kotlin.plugin.serialization" version "1.8.10" apply false
|
id "org.jetbrains.kotlin.plugin.serialization" version "1.8.10" apply false
|
||||||
id 'com.android.test' version '7.4.2' apply false
|
id 'com.android.test' version '8.1.1' apply false
|
||||||
id 'com.google.devtools.ksp' version '1.8.10-1.0.9' apply false
|
id 'com.google.devtools.ksp' version '1.8.10-1.0.9' apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
6
gradle/wrapper/gradle-wrapper.properties
vendored
6
gradle/wrapper/gradle-wrapper.properties
vendored
|
|
@ -1,6 +1,6 @@
|
||||||
#Sat Apr 08 15:57:50 CEST 2023
|
#Wed Oct 18 18:36:39 CEST 2023
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
|
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStorePath=wrapper/dists
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue