alot of stuff
-added download now option when u highlight only scheduled items in the context menu -tappin on the errored notification will send you to errored tab, if you have logs disabled. If logs on it will send u to the log -added preferred format ordering. By ID, FILESIZE, CONTAINER. Formats grouped by container will also be sorted by filesize -fixed app not showing formats when u try to modify a current download item and its a different type (audio formats not showing if its a video type and vice versa) -fixed app showing generic formats in cases where the format length was the same as that of generic formats (silly mistake) -made the app store static strings for 'best' and 'worst' so that there isnt any confusion when u try to change the language and the stored downloads will have the other language's string -added collected filesize on top of the download queue -potentially fixed app not crashing when going to queued screen? idk -fixed app not moving files when its a fresh install and u havent tried to change the download path. Now the app will just straight up download in that path if it finds that it can write in it. If not, it will use the old method of moving through URI's -added download retries options --retries and --fragment-retries
This commit is contained in:
parent
b25d8410d1
commit
b1b5dbc608
31 changed files with 315 additions and 150 deletions
|
|
@ -10,7 +10,7 @@ def properties = new Properties()
|
|||
def versionMajor = 1
|
||||
def versionMinor = 6
|
||||
def versionPatch = 3
|
||||
def versionBuild = 1 // bump for dogfood builds, public betas, etc.
|
||||
def versionBuild = 2 // bump for dogfood builds, public betas, etc.
|
||||
def versionExt = ""
|
||||
|
||||
if (versionBuild > 0){
|
||||
|
|
@ -64,7 +64,8 @@ android {
|
|||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
debuggable false
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
|
|
@ -157,7 +158,7 @@ dependencies {
|
|||
androidTestImplementation "junit:junit:$junitVer"
|
||||
androidTestImplementation "androidx.test.ext:junit:$androidJunitVer"
|
||||
androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVer"
|
||||
androidTestImplementation "androidx.benchmark:benchmark-macro-junit4:1.2.0-alpha13"
|
||||
androidTestImplementation "androidx.benchmark:benchmark-macro-junit4:1.2.0-alpha16"
|
||||
androidTestImplementation "androidx.test:runner:1.5.2"
|
||||
androidTestImplementation "androidx.test:core:1.5.0"
|
||||
androidTestImplementation "androidx.test:rules:1.5.0"
|
||||
|
|
@ -175,10 +176,10 @@ dependencies {
|
|||
|
||||
implementation "androidx.work:work-runtime-ktx:$workVer"
|
||||
|
||||
implementation "androidx.room:room-runtime:2.5.1"
|
||||
implementation "androidx.room:room-ktx:2.5.1"
|
||||
kapt "androidx.room:room-compiler:2.5.1"
|
||||
androidTestImplementation "androidx.room:room-testing:2.5.1"
|
||||
implementation "androidx.room:room-runtime:2.5.2"
|
||||
implementation "androidx.room:room-ktx:2.5.2"
|
||||
kapt "androidx.room:room-compiler:2.5.2"
|
||||
androidTestImplementation "androidx.room:room-testing:2.5.2"
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'
|
||||
implementation "androidx.compose.runtime:runtime:$composeVer"
|
||||
androidTestImplementation "com.google.truth:truth:1.1.3"
|
||||
|
|
@ -190,7 +191,7 @@ dependencies {
|
|||
implementation "com.squareup.okhttp3:okhttp:5.0.0-alpha.10"
|
||||
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0'
|
||||
implementation "com.google.android.exoplayer:exoplayer:2.18.5"
|
||||
implementation "com.google.android.exoplayer:exoplayer:2.18.7"
|
||||
implementation 'it.xabaras.android:recyclerview-swipedecorator:1.4'
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.1"
|
||||
implementation "com.github.Irineu333:Highlight-KT:1.0.4"
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
package com.deniscerri.ytdlnis.database.dao
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.room.*
|
||||
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
||||
import com.deniscerri.ytdlnis.database.models.TemplateShortcut
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface CommandTemplateDao {
|
||||
|
|
@ -11,10 +11,10 @@ interface CommandTemplateDao {
|
|||
fun getAllTemplates() : List<CommandTemplate>
|
||||
|
||||
@Query("SELECT * FROM commandTemplates ORDER BY id DESC")
|
||||
fun getAllTemplatesLiveData() : LiveData<List<CommandTemplate>>
|
||||
fun getAllTemplatesFlow() : Flow<List<CommandTemplate>>
|
||||
|
||||
@Query("SELECT * FROM templateShortcuts ORDER BY id DESC")
|
||||
fun getAllShortcutsLiveData() : LiveData<List<TemplateShortcut>>
|
||||
fun getAllShortcutsFlow() : Flow<List<TemplateShortcut>>
|
||||
|
||||
@Query("SELECT * FROM templateShortcuts ORDER BY id DESC")
|
||||
fun getAllShortcuts() : List<TemplateShortcut>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package com.deniscerri.ytdlnis.database.dao
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.room.*
|
||||
import com.deniscerri.ytdlnis.database.models.CookieItem
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface CookieDao {
|
||||
|
|
@ -10,7 +10,7 @@ interface CookieDao {
|
|||
fun getAllCookies() : List<CookieItem>
|
||||
|
||||
@Query("SELECT * FROM cookies ORDER BY id DESC")
|
||||
fun getAllCookiesLiveData() : LiveData<List<CookieItem>>
|
||||
fun getAllCookiesFlow() : Flow<List<CookieItem>>
|
||||
|
||||
@Query("SELECT EXISTS(SELECT * FROM cookies WHERE url=:url LIMIT 1)")
|
||||
fun checkIfExistsWithSameURL(url: String) : Boolean
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
package com.deniscerri.ytdlnis.database.dao
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.room.*
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface DownloadDao {
|
||||
|
||||
@Query("SELECT * FROM downloads ORDER BY status")
|
||||
fun getAllDownloads() : LiveData<List<DownloadItem>>
|
||||
fun getAllDownloads() : Flow<List<DownloadItem>>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Active'")
|
||||
fun getActiveDownloads() : LiveData<List<DownloadItem>>
|
||||
fun getActiveDownloads() : Flow<List<DownloadItem>>
|
||||
|
||||
@Query("SELECT COUNT(*) FROM downloads WHERE status='Active'")
|
||||
fun getActiveDownloadsCount() : LiveData<Int>
|
||||
fun getActiveDownloadsCount() : Flow<Int>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Active'")
|
||||
fun getActiveDownloadsList() : List<DownloadItem>
|
||||
|
|
@ -23,25 +23,25 @@ interface DownloadDao {
|
|||
fun getActiveAndQueuedDownloadsList() : List<DownloadItem>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Queued' ORDER BY downloadStartTime, id")
|
||||
fun getQueuedDownloads() : LiveData<List<DownloadItem>>
|
||||
fun getQueuedDownloads() : Flow<List<DownloadItem>>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Queued' ORDER BY downloadStartTime, id")
|
||||
fun getQueuedDownloadsList() : List<DownloadItem>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Cancelled' ORDER BY id DESC")
|
||||
fun getCancelledDownloads() : LiveData<List<DownloadItem>>
|
||||
fun getCancelledDownloads() : Flow<List<DownloadItem>>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Cancelled' ORDER BY id DESC")
|
||||
fun getCancelledDownloadsList() : List<DownloadItem>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Error' ORDER BY id DESC")
|
||||
fun getErroredDownloads() : LiveData<List<DownloadItem>>
|
||||
fun getErroredDownloads() : Flow<List<DownloadItem>>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Error' ORDER BY id DESC")
|
||||
fun getErroredDownloadsList() : List<DownloadItem>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Processing' ORDER BY id DESC")
|
||||
fun getProcessingDownloads() : LiveData<List<DownloadItem>>
|
||||
fun getProcessingDownloads() : Flow<List<DownloadItem>>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE id=:id LIMIT 1")
|
||||
fun getDownloadById(id: Long) : DownloadItem
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package com.deniscerri.ytdlnis.database.dao
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.room.*
|
||||
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface HistoryDao {
|
||||
|
|
@ -27,7 +27,7 @@ interface HistoryDao {
|
|||
|
||||
|
||||
@Query("SELECT * FROM history")
|
||||
fun getAllHistory() : LiveData<List<HistoryItem>>
|
||||
fun getAllHistory() : Flow<List<HistoryItem>>
|
||||
|
||||
@Query("SELECT * FROM history")
|
||||
fun getAllHistoryList() : List<HistoryItem>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
package com.deniscerri.ytdlnis.database.dao
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.room.*
|
||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface ResultDao {
|
||||
@Query("SELECT * FROM results")
|
||||
fun getResults() : LiveData<List<ResultItem>>
|
||||
fun getResults() : Flow<List<ResultItem>>
|
||||
|
||||
@Query("SELECT COUNT(id) FROM results")
|
||||
fun getCount() : LiveData<Int>
|
||||
fun getCount() : Flow<Int>
|
||||
|
||||
@Query("SELECT COUNT(id) FROM results")
|
||||
fun getCountInt() :Int
|
||||
|
|
|
|||
|
|
@ -4,10 +4,11 @@ import androidx.lifecycle.LiveData
|
|||
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
|
||||
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
||||
import com.deniscerri.ytdlnis.database.models.TemplateShortcut
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class CommandTemplateRepository(private val commandDao: CommandTemplateDao) {
|
||||
val items : LiveData<List<CommandTemplate>> = commandDao.getAllTemplatesLiveData()
|
||||
val shortcuts : LiveData<List<TemplateShortcut>> = commandDao.getAllShortcutsLiveData()
|
||||
val items : Flow<List<CommandTemplate>> = commandDao.getAllTemplatesFlow()
|
||||
val shortcuts : Flow<List<TemplateShortcut>> = commandDao.getAllShortcutsFlow()
|
||||
|
||||
fun getAll() : List<CommandTemplate> {
|
||||
return commandDao.getAllTemplates()
|
||||
|
|
|
|||
|
|
@ -3,9 +3,10 @@ package com.deniscerri.ytdlnis.database.repository
|
|||
import androidx.lifecycle.LiveData
|
||||
import com.deniscerri.ytdlnis.database.dao.CookieDao
|
||||
import com.deniscerri.ytdlnis.database.models.CookieItem
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class CookieRepository(private val cookieDao: CookieDao) {
|
||||
val items : LiveData<List<CookieItem>> = cookieDao.getAllCookiesLiveData()
|
||||
val items : Flow<List<CookieItem>> = cookieDao.getAllCookiesFlow()
|
||||
|
||||
fun getAll() : List<CookieItem> {
|
||||
return cookieDao.getAllCookies()
|
||||
|
|
|
|||
|
|
@ -4,15 +4,16 @@ import androidx.lifecycle.LiveData
|
|||
import com.deniscerri.ytdlnis.database.Converters
|
||||
import com.deniscerri.ytdlnis.database.dao.DownloadDao
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class DownloadRepository(private val downloadDao: DownloadDao) {
|
||||
val allDownloads : LiveData<List<DownloadItem>> = downloadDao.getAllDownloads()
|
||||
val activeDownloads : LiveData<List<DownloadItem>> = downloadDao.getActiveDownloads()
|
||||
val activeDownloadsCount : LiveData<Int> = downloadDao.getActiveDownloadsCount()
|
||||
val queuedDownloads : LiveData<List<DownloadItem>> = downloadDao.getQueuedDownloads()
|
||||
val cancelledDownloads : LiveData<List<DownloadItem>> = downloadDao.getCancelledDownloads()
|
||||
val erroredDownloads : LiveData<List<DownloadItem>> = downloadDao.getErroredDownloads()
|
||||
val processingDownloads : LiveData<List<DownloadItem>> = downloadDao.getProcessingDownloads()
|
||||
val allDownloads : Flow<List<DownloadItem>> = downloadDao.getAllDownloads()
|
||||
val activeDownloads : Flow<List<DownloadItem>> = downloadDao.getActiveDownloads()
|
||||
val activeDownloadsCount : Flow<Int> = downloadDao.getActiveDownloadsCount()
|
||||
val queuedDownloads : Flow<List<DownloadItem>> = downloadDao.getQueuedDownloads()
|
||||
val cancelledDownloads : Flow<List<DownloadItem>> = downloadDao.getCancelledDownloads()
|
||||
val erroredDownloads : Flow<List<DownloadItem>> = downloadDao.getErroredDownloads()
|
||||
val processingDownloads : Flow<List<DownloadItem>> = downloadDao.getProcessingDownloads()
|
||||
|
||||
enum class Status {
|
||||
Active, Queued, Error, Processing, Cancelled
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@ import androidx.lifecycle.LiveData
|
|||
import com.deniscerri.ytdlnis.database.dao.HistoryDao
|
||||
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
|
||||
class HistoryRepository(private val historyDao: HistoryDao) {
|
||||
val items : LiveData<List<HistoryItem>> = historyDao.getAllHistory()
|
||||
val items : Flow<List<HistoryItem>> = historyDao.getAllHistory()
|
||||
enum class HistorySort{
|
||||
DESC, ASC
|
||||
}
|
||||
|
|
@ -59,9 +61,11 @@ class HistoryRepository(private val historyDao: HistoryDao) {
|
|||
}
|
||||
|
||||
suspend fun clearDeletedHistory(){
|
||||
items.value?.forEach { item ->
|
||||
if (!FileUtil.exists(item.downloadPath)){
|
||||
historyDao.delete(item.id)
|
||||
items.collectLatest {
|
||||
it.forEach { item ->
|
||||
if (!FileUtil.exists(item.downloadPath)){
|
||||
historyDao.delete(item.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,18 +2,15 @@ package com.deniscerri.ytdlnis.database.repository
|
|||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
|
||||
import com.deniscerri.ytdlnis.database.dao.ResultDao
|
||||
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
class ResultRepository(private val resultDao: ResultDao, private val commandTemplateDao: CommandTemplateDao, private val context: Context) {
|
||||
class ResultRepository(private val resultDao: ResultDao, private val context: Context) {
|
||||
private val tag: String = "ResultRepository"
|
||||
val allResults : LiveData<List<ResultItem>> = resultDao.getResults()
|
||||
val allResults : Flow<List<ResultItem>> = resultDao.getResults()
|
||||
var itemCount = MutableStateFlow(-1)
|
||||
|
||||
suspend fun insert(it: ResultItem){
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import android.content.ClipboardManager
|
|||
import android.content.Context.CLIPBOARD_SERVICE
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.deniscerri.ytdlnis.database.DBManager
|
||||
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
||||
|
|
@ -27,8 +28,8 @@ class CommandTemplateViewModel(private val application: Application) : AndroidVi
|
|||
init {
|
||||
val dao = DBManager.getInstance(application).commandTemplateDao
|
||||
repository = CommandTemplateRepository(dao)
|
||||
items = repository.items
|
||||
shortcuts = repository.shortcuts
|
||||
items = repository.items.asLiveData()
|
||||
shortcuts = repository.shortcuts.asLiveData()
|
||||
}
|
||||
|
||||
fun getTemplate(itemId: Long): CommandTemplate {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import android.util.Log
|
|||
import android.webkit.CookieManager
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.deniscerri.ytdlnis.database.DBManager
|
||||
import com.deniscerri.ytdlnis.database.models.CookieItem
|
||||
|
|
@ -31,7 +32,7 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a
|
|||
init {
|
||||
val dao = DBManager.getInstance(application).cookieDao
|
||||
repository = CookieRepository(dao)
|
||||
items = repository.items
|
||||
items = repository.items.asLiveData()
|
||||
}
|
||||
|
||||
fun getAll(): List<CookieItem> {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import android.util.DisplayMetrics
|
|||
import android.widget.Toast
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.work.Constraints
|
||||
|
|
@ -77,13 +78,13 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
commandTemplateDao = DBManager.getInstance(application).commandTemplateDao
|
||||
infoUtil = InfoUtil(application)
|
||||
|
||||
allDownloads = repository.allDownloads
|
||||
queuedDownloads = repository.queuedDownloads
|
||||
activeDownloads = repository.activeDownloads
|
||||
activeDownloadsCount = repository.activeDownloadsCount
|
||||
processingDownloads = repository.processingDownloads
|
||||
cancelledDownloads = repository.cancelledDownloads
|
||||
erroredDownloads = repository.erroredDownloads
|
||||
allDownloads = repository.allDownloads.asLiveData()
|
||||
queuedDownloads = repository.queuedDownloads.asLiveData()
|
||||
activeDownloads = repository.activeDownloads.asLiveData()
|
||||
activeDownloadsCount = repository.activeDownloadsCount.asLiveData()
|
||||
processingDownloads = repository.processingDownloads.asLiveData()
|
||||
cancelledDownloads = repository.cancelledDownloads.asLiveData()
|
||||
erroredDownloads = repository.erroredDownloads.asLiveData()
|
||||
|
||||
videoQualityPreference = sharedPreferences.getString("video_quality", application.getString(R.string.best_quality)).toString()
|
||||
formatIDPreference = sharedPreferences.getString("format_id", "").toString()
|
||||
|
|
@ -95,15 +96,14 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
resources = Resources(application.assets, metrics, confTmp)
|
||||
|
||||
|
||||
val videoFormat = resources.getStringArray(R.array.video_formats)
|
||||
val videoFormatValues = resources.getStringArray(R.array.video_formats_values)
|
||||
var videoContainer = sharedPreferences.getString("video_format", "Default")
|
||||
if (videoContainer == "Default") videoContainer = App.instance.getString(R.string.defaultValue)
|
||||
|
||||
defaultVideoFormats = mutableListOf()
|
||||
videoFormat.forEachIndexed { index , it ->
|
||||
videoFormatValues.forEach {
|
||||
val tmp = Format(
|
||||
videoFormatValues[index],
|
||||
it,
|
||||
videoContainer!!,
|
||||
"",
|
||||
"",
|
||||
|
|
@ -125,7 +125,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
"",
|
||||
"",
|
||||
0,
|
||||
resources.getString(R.string.best_quality)
|
||||
"best"
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -381,7 +381,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
}
|
||||
|
||||
fun getGenericVideoFormats() : MutableList<Format>{
|
||||
val videoFormats = resources.getStringArray(R.array.video_formats)
|
||||
val videoFormats = resources.getStringArray(R.array.video_formats_values)
|
||||
val formats = mutableListOf<Format>()
|
||||
var containerPreference = sharedPreferences.getString("video_format", "Default")
|
||||
if (containerPreference == "Default") containerPreference = resources.getString(R.string.defaultValue)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import androidx.lifecycle.AndroidViewModel
|
|||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MediatorLiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.deniscerri.ytdlnis.database.DBManager
|
||||
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||
|
|
@ -28,7 +29,7 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application)
|
|||
init {
|
||||
val dao = DBManager.getInstance(application).historyDao
|
||||
repository = HistoryRepository(dao)
|
||||
allItems = repository.items
|
||||
allItems = repository.items.asLiveData()
|
||||
|
||||
_items.addSource(allItems){
|
||||
filter(queryFilter.value!!, formatFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!)
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ package com.deniscerri.ytdlnis.database.viewmodel
|
|||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.deniscerri.ytdlnis.App
|
||||
|
|
@ -17,7 +17,6 @@ import com.deniscerri.ytdlnis.database.models.SearchHistoryItem
|
|||
import com.deniscerri.ytdlnis.database.repository.ResultRepository
|
||||
import com.deniscerri.ytdlnis.database.repository.SearchHistoryRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.regex.Pattern
|
||||
|
|
@ -33,9 +32,9 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
|
|||
init {
|
||||
val dao = DBManager.getInstance(application).resultDao
|
||||
val commandDao = DBManager.getInstance(application).commandTemplateDao
|
||||
repository = ResultRepository(dao, commandDao, getApplication<Application>().applicationContext)
|
||||
repository = ResultRepository(dao, getApplication<Application>().applicationContext)
|
||||
searchHistoryRepository = SearchHistoryRepository(DBManager.getInstance(application).searchHistoryDao)
|
||||
items = repository.allResults
|
||||
items = repository.allResults.asLiveData()
|
||||
loadingItems.postValue(false)
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
lifecycleScope.launch {
|
||||
downloadItem = withContext(Dispatchers.IO) {
|
||||
if (currentDownloadItem != null && currentDownloadItem!!.type == Type.audio){
|
||||
if (currentDownloadItem != null){
|
||||
val string = Gson().toJson(currentDownloadItem, DownloadItem::class.java)
|
||||
Gson().fromJson(string, DownloadItem::class.java)
|
||||
}else{
|
||||
|
|
@ -126,7 +126,17 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
|
|||
if (free == "?") freeSpace.visibility = View.GONE
|
||||
|
||||
var formats = mutableListOf<Format>()
|
||||
formats.addAll(resultItem.formats.filter { it.format_note.contains("audio", ignoreCase = true) })
|
||||
if (currentDownloadItem == null) {
|
||||
formats.addAll(resultItem.formats.filter { it.format_note.contains("audio", ignoreCase = true) })
|
||||
}else{
|
||||
//if its updating a present downloaditem and its the wrong category
|
||||
if (currentDownloadItem!!.type != Type.audio){
|
||||
downloadItem.type = Type.audio
|
||||
downloadItem.format =
|
||||
downloadItem.allFormats.filter { it.format_note.contains("audio", ignoreCase = true) }
|
||||
.maxByOrNull { it.filesize }!!
|
||||
}
|
||||
}
|
||||
if (formats.isEmpty()) formats.addAll(downloadItem.allFormats.filter { it.format_note.contains("audio", ignoreCase = true) })
|
||||
|
||||
val containers = requireContext().resources.getStringArray(R.array.audio_containers)
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
lifecycleScope.launch {
|
||||
downloadItem = withContext(Dispatchers.IO){
|
||||
if (currentDownloadItem != null && currentDownloadItem!!.type == Type.video){
|
||||
if (currentDownloadItem != null){
|
||||
val string = Gson().toJson(currentDownloadItem, DownloadItem::class.java)
|
||||
Gson().fromJson(string, DownloadItem::class.java)
|
||||
}else{
|
||||
|
|
@ -127,8 +127,19 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
|
|||
freeSpace.text = String.format( getString(R.string.freespace) + ": " + free)
|
||||
if (free == "?") freeSpace.visibility = View.GONE
|
||||
|
||||
|
||||
var formats = mutableListOf<Format>()
|
||||
formats.addAll(resultItem.formats)
|
||||
if (currentDownloadItem == null) {
|
||||
formats.addAll(resultItem.formats)
|
||||
}else{
|
||||
//if its updating a present downloaditem and its the wrong category
|
||||
if (currentDownloadItem!!.type != Type.video){
|
||||
downloadItem.type = Type.video
|
||||
downloadItem.format =
|
||||
downloadItem.allFormats.filter { it.vcodec.isNotEmpty() }
|
||||
.maxByOrNull { it.filesize }!!
|
||||
}
|
||||
}
|
||||
if (formats.isEmpty()) formats.addAll(downloadItem.allFormats)
|
||||
|
||||
val containers = requireContext().resources.getStringArray(R.array.video_containers)
|
||||
|
|
|
|||
|
|
@ -15,9 +15,7 @@ import androidx.preference.PreferenceManager
|
|||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
import com.deniscerri.ytdlnis.database.models.Format
|
||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
import com.facebook.shimmer.ShimmerFrameLayout
|
||||
|
|
@ -27,6 +25,7 @@ import com.google.android.material.card.MaterialCardView
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.internal.format
|
||||
import java.util.*
|
||||
|
||||
|
||||
|
|
@ -45,6 +44,11 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
|||
private lateinit var videoTitle : TextView
|
||||
private lateinit var audioTitle : TextView
|
||||
|
||||
private lateinit var sortBy : FormatSorting
|
||||
enum class FormatSorting {
|
||||
filesize, container, id
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
infoUtil = InfoUtil(requireActivity().applicationContext)
|
||||
|
|
@ -61,6 +65,8 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
|||
val view = LayoutInflater.from(context).inflate(R.layout.format_select_bottom_sheet, null)
|
||||
dialog.setContentView(view)
|
||||
|
||||
sortBy = FormatSorting.valueOf(sharedPreferences.getString("format_order", "filesize")!!)
|
||||
|
||||
dialog.setOnShowListener {
|
||||
behavior = BottomSheetBehavior.from(view.parent as View)
|
||||
val displayMetrics = DisplayMetrics()
|
||||
|
|
@ -78,10 +84,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
|||
okBtn = view.findViewById(R.id.format_ok)
|
||||
|
||||
shimmers.visibility = View.GONE
|
||||
val hasGenericFormats = when(items.first()!!.type){
|
||||
Type.audio -> formats.first().size == resources.getStringArray(R.array.audio_formats).size
|
||||
else -> formats.first().size == resources.getStringArray(R.array.video_formats).size
|
||||
}
|
||||
val hasGenericFormats = formats.first().isEmpty() || formats.last().any { it.format_id == "best" }
|
||||
if (items.size > 1){
|
||||
|
||||
if (!hasGenericFormats){
|
||||
|
|
@ -131,10 +134,10 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
|||
infoUtil.getFormats(items.first()!!.url)
|
||||
}
|
||||
res.filter { it.format_note != "storyboard" }
|
||||
if(items.first()?.type == Type.audio){
|
||||
chosenFormats = res.filter { it.format_note.contains("audio", ignoreCase = true) }
|
||||
chosenFormats = if(items.first()?.type == Type.audio){
|
||||
res.filter { it.format_note.contains("audio", ignoreCase = true) }
|
||||
}else{
|
||||
chosenFormats = res
|
||||
res
|
||||
}
|
||||
if (chosenFormats.isEmpty()) throw Exception()
|
||||
//playlist format filtering
|
||||
|
|
@ -216,7 +219,14 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
|||
}
|
||||
}
|
||||
private fun addFormatsToView(){
|
||||
val canMultiSelectAudio = items.first()?.type == Type.video && chosenFormats.find { it.format_note.contains("audio", ignoreCase = true) } != null
|
||||
//sort
|
||||
val finalFormats: List<Format> = when(sortBy){
|
||||
FormatSorting.container -> chosenFormats.groupBy { it.container }.flatMap { it.value }
|
||||
FormatSorting.id -> chosenFormats.sortedBy { it.format_id }
|
||||
FormatSorting.filesize -> chosenFormats
|
||||
}
|
||||
|
||||
val canMultiSelectAudio = items.first()?.type == Type.video && finalFormats.find { it.format_note.contains("audio", ignoreCase = true) } != null
|
||||
videoFormatList.removeAllViews()
|
||||
audioFormatList.removeAllViews()
|
||||
|
||||
|
|
@ -226,7 +236,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
|||
audioTitle.visibility = View.GONE
|
||||
okBtn.visibility = View.GONE
|
||||
}else{
|
||||
if (chosenFormats.count { it.vcodec.isBlank() || it.vcodec == "none" } == 0){
|
||||
if (finalFormats.count { it.vcodec.isBlank() || it.vcodec == "none" } == 0){
|
||||
audioFormatList.visibility = View.GONE
|
||||
audioTitle.visibility = View.GONE
|
||||
videoTitle.visibility = View.GONE
|
||||
|
|
@ -239,8 +249,8 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
|||
}
|
||||
}
|
||||
|
||||
for (i in chosenFormats.lastIndex downTo 0){
|
||||
val format = chosenFormats[i]
|
||||
for (i in finalFormats.lastIndex downTo 0){
|
||||
val format = finalFormats[i]
|
||||
val formatItem = LayoutInflater.from(context).inflate(R.layout.format_item, null)
|
||||
formatItem.tag = "${format.format_id}${format.format_note}"
|
||||
UiUtil.populateFormatCard(formatItem as MaterialCardView, format, null)
|
||||
|
|
@ -250,7 +260,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
|||
val clickedCard = (clickedformat as MaterialCardView)
|
||||
if (format.vcodec.isNotBlank() && format.vcodec != "none") {
|
||||
if (clickedCard.isChecked) {
|
||||
listener.onFormatClick(List(items.size){chosenFormats}, listOf(FormatTuple(format, null)))
|
||||
listener.onFormatClick(List(items.size){finalFormats}, listOf(FormatTuple(format, null)))
|
||||
dismiss()
|
||||
}
|
||||
videoFormatList.forEach { (it as MaterialCardView).isChecked = false }
|
||||
|
|
@ -269,14 +279,14 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
|||
}
|
||||
}else{
|
||||
if (items.size == 1){
|
||||
listener.onFormatClick(List(items.size){chosenFormats}, listOf(FormatTuple(format, null)))
|
||||
listener.onFormatClick(List(items.size){finalFormats}, listOf(FormatTuple(format, null)))
|
||||
}else{
|
||||
val selectedFormats = mutableListOf<Format>()
|
||||
formatCollection.forEach {
|
||||
selectedFormats.add(it.first{ f -> f.format_id == format.format_id})
|
||||
}
|
||||
if (selectedFormats.isEmpty()) {
|
||||
items.forEach {
|
||||
items.forEach { _ ->
|
||||
selectedFormats.add(format)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,6 +102,10 @@ class DownloadQueueMainFragment : Fragment(){
|
|||
})
|
||||
mainActivity.hideBottomNavigation()
|
||||
initMenu()
|
||||
|
||||
if (arguments?.getString("tab") != null){
|
||||
tabLayout.selectTab(tabLayout.getTabAt(3))
|
||||
}
|
||||
}
|
||||
|
||||
private fun initMenu() {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.deniscerri.ytdlnis.ui.downloads
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.DialogInterface
|
||||
import android.graphics.Canvas
|
||||
|
|
@ -59,6 +60,7 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
|||
private lateinit var queuedRecyclerView : RecyclerView
|
||||
private lateinit var queuedDownloads : GenericDownloadAdapter
|
||||
private lateinit var notificationUtil: NotificationUtil
|
||||
private lateinit var fileSize: TextView
|
||||
private var selectedObjects: ArrayList<DownloadItem>? = null
|
||||
private var actionMode : ActionMode? = null
|
||||
private lateinit var items : MutableList<DownloadItem>
|
||||
|
|
@ -68,7 +70,7 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
|||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
fragmentView = inflater.inflate(R.layout.fragment_generic_download_queue, container, false)
|
||||
fragmentView = inflater.inflate(R.layout.fragment_inqueue, container, false)
|
||||
activity = getActivity()
|
||||
notificationUtil = NotificationUtil(requireContext())
|
||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||
|
|
@ -77,9 +79,10 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
|||
return fragmentView
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
fileSize = view.findViewById(R.id.filesize)
|
||||
queuedDownloads =
|
||||
GenericDownloadAdapter(
|
||||
this,
|
||||
|
|
@ -97,6 +100,11 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
|||
|
||||
downloadViewModel.queuedDownloads.observe(viewLifecycleOwner) {
|
||||
items = it.toMutableList()
|
||||
if (it.isEmpty()) fileSize.visibility = View.GONE
|
||||
else{
|
||||
fileSize.visibility = View.VISIBLE
|
||||
fileSize.text = "${getString(R.string.file_size)}: ~ ${FileUtil.convertFileSize(it.sumOf { i -> i.format.filesize })}"
|
||||
}
|
||||
queuedDownloads.submitList(it)
|
||||
}
|
||||
}
|
||||
|
|
@ -232,6 +240,7 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
|||
|
||||
override fun onCardSelect(itemID: Long, isChecked: Boolean) {
|
||||
val item = items.find { it.id == itemID }
|
||||
val now = System.currentTimeMillis()
|
||||
if (isChecked) {
|
||||
selectedObjects!!.add(item!!)
|
||||
if (actionMode == null){
|
||||
|
|
@ -248,6 +257,9 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
|||
actionMode?.finish()
|
||||
}
|
||||
}
|
||||
if (actionMode != null){
|
||||
actionMode!!.menu.getItem(1).isVisible = selectedObjects!!.all { it.downloadStartTime > now }
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeItem(itemID: Long){
|
||||
|
|
@ -311,6 +323,16 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
|||
deleteDialog.show()
|
||||
true
|
||||
}
|
||||
R.id.download -> {
|
||||
for (obj in selectedObjects!!){
|
||||
WorkManager.getInstance(requireContext()).cancelUniqueWork(obj.id.toInt().toString())
|
||||
}
|
||||
selectedObjects!!.forEach { it.downloadStartTime = 0L }
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
downloadViewModel.queueDownloads(selectedObjects!!)
|
||||
}
|
||||
true
|
||||
}
|
||||
R.id.select_all -> {
|
||||
queuedDownloads.checkAll(items)
|
||||
selectedObjects?.clear()
|
||||
|
|
|
|||
|
|
@ -90,39 +90,28 @@ object FileUtil {
|
|||
|
||||
if(it.name.contains(".part-Frag")) return@forEach
|
||||
|
||||
//sending to main or SD CARD
|
||||
if (currentDirectory.exists() && Build.VERSION.SDK_INT >= 26){
|
||||
if (Build.VERSION.SDK_INT >= 26 ){
|
||||
Files.move(it.toPath(), destFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
||||
}else{
|
||||
it.renameTo(destFile)
|
||||
}
|
||||
fileList.add(destFile)
|
||||
}else{
|
||||
//sending to USB OTG
|
||||
val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(it.extension) ?: "*/*"
|
||||
destFile = File(currentDirectory.absolutePath + "/${it.name}")
|
||||
val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(it.extension) ?: "*/*"
|
||||
destFile = File(currentDirectory.absolutePath + "/${it.name}")
|
||||
|
||||
val dest = Uri.parse(destDir).run {
|
||||
DocumentsContract.buildDocumentUriUsingTree(
|
||||
this,
|
||||
DocumentsContract.getTreeDocumentId(this)
|
||||
)
|
||||
}
|
||||
val destUri = DocumentsContract.createDocument(
|
||||
context.contentResolver,
|
||||
dest,
|
||||
mimeType,
|
||||
it.name
|
||||
) ?: return@forEach
|
||||
|
||||
val inputStream = it.inputStream()
|
||||
val outputStream =
|
||||
context.contentResolver.openOutputStream(destUri) ?: return@forEach
|
||||
inputStream.copyTo(outputStream)
|
||||
inputStream.closeQuietly()
|
||||
outputStream.closeQuietly()
|
||||
val dest = Uri.parse(destDir).run {
|
||||
DocumentsContract.buildDocumentUriUsingTree(
|
||||
this,
|
||||
DocumentsContract.getTreeDocumentId(this)
|
||||
)
|
||||
}
|
||||
val destUri = DocumentsContract.createDocument(
|
||||
context.contentResolver,
|
||||
dest,
|
||||
mimeType,
|
||||
it.name
|
||||
) ?: return@forEach
|
||||
|
||||
val inputStream = it.inputStream()
|
||||
val outputStream =
|
||||
context.contentResolver.openOutputStream(destUri) ?: return@forEach
|
||||
inputStream.copyTo(outputStream)
|
||||
inputStream.closeQuietly()
|
||||
outputStream.closeQuietly()
|
||||
|
||||
fileList.add(destFile)
|
||||
}catch (e: java.lang.Exception) {
|
||||
|
|
|
|||
|
|
@ -245,6 +245,15 @@ class NotificationUtil(var context: Context) {
|
|||
.setArguments(bundle)
|
||||
.createPendingIntent()
|
||||
|
||||
val tabBundle = Bundle()
|
||||
tabBundle.putString("tab", "error")
|
||||
|
||||
val errorTabPendingIntent = NavDeepLinkBuilder(context)
|
||||
.setGraph(R.navigation.nav_graph)
|
||||
.setDestination(R.id.downloadQueueMainFragment)
|
||||
.setArguments(tabBundle)
|
||||
.createPendingIntent()
|
||||
|
||||
notificationBuilder
|
||||
.setContentTitle("${context.getString(R.string.failed_download)}: $title")
|
||||
.setContentText(error)
|
||||
|
|
@ -255,6 +264,7 @@ class NotificationUtil(var context: Context) {
|
|||
R.drawable.ic_launcher_foreground_large
|
||||
)
|
||||
)
|
||||
.setContentIntent(errorTabPendingIntent)
|
||||
.setPriority(NotificationCompat.PRIORITY_MAX)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.clearActions()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.deniscerri.ytdlnis.work
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
|
|
@ -91,6 +92,16 @@ class DownloadWorker(
|
|||
tempFileDir.delete()
|
||||
tempFileDir.mkdirs()
|
||||
|
||||
var useCacheFolder = true
|
||||
val downloadLocationFile = File(downloadLocation)
|
||||
if (downloadLocationFile.canWrite()) {
|
||||
File(downloadLocation).mkdirs()
|
||||
if(Build.VERSION.SDK_INT > 23) request.addOption("-P", "temp:" + tempFileDir.absolutePath)
|
||||
useCacheFolder = false
|
||||
}
|
||||
val pathUsed = if (useCacheFolder) tempFileDir.absolutePath else downloadLocation
|
||||
|
||||
|
||||
val aria2 = sharedPreferences.getBoolean("aria2", false)
|
||||
if (aria2) {
|
||||
request.addOption("--downloader", "libaria2c.so")
|
||||
|
|
@ -99,6 +110,13 @@ class DownloadWorker(
|
|||
val concurrentFragments = sharedPreferences.getInt("concurrent_fragments", 1)
|
||||
if (concurrentFragments > 1) request.addOption("-N", concurrentFragments)
|
||||
}
|
||||
|
||||
val retries = sharedPreferences.getString("--retries", "")!!
|
||||
val fragmentRetries = sharedPreferences.getString("--fragment_retries", "")!!
|
||||
|
||||
if(retries.isNotEmpty()) request.addOption("retries", retries)
|
||||
if(fragmentRetries.isNotEmpty()) request.addOption("fragment-retries", fragmentRetries)
|
||||
|
||||
val limitRate = sharedPreferences.getString("limit_rate", "")
|
||||
if (limitRate != "") request.addOption("-r", limitRate!!)
|
||||
if(downloadItem.type != DownloadViewModel.Type.command){
|
||||
|
|
@ -215,9 +233,9 @@ class DownloadWorker(
|
|||
|
||||
if (downloadItem.audioPreferences.splitByChapters && downloadItem.downloadSections.isBlank()){
|
||||
request.addOption("--split-chapters")
|
||||
request.addOption("-P", tempFileDir.absolutePath)
|
||||
request.addOption("-P", pathUsed)
|
||||
}else{
|
||||
request.addOption("-o", tempFileDir.absolutePath + "/${downloadItem.customFileNameTemplate}.%(ext)s")
|
||||
request.addOption("-o", pathUsed + "/${downloadItem.customFileNameTemplate}.%(ext)s")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -281,9 +299,9 @@ class DownloadWorker(
|
|||
|
||||
if (downloadItem.videoPreferences.splitByChapters && downloadItem.downloadSections.isBlank()){
|
||||
request.addOption("--split-chapters")
|
||||
request.addOption("-P", tempFileDir.absolutePath)
|
||||
request.addOption("-P", pathUsed)
|
||||
}else{
|
||||
request.addOption("-o", tempFileDir.absolutePath + "/${downloadItem.customFileNameTemplate}.%(ext)s")
|
||||
request.addOption("-o", pathUsed + "/${downloadItem.customFileNameTemplate}.%(ext)s")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -294,7 +312,7 @@ class DownloadWorker(
|
|||
writeText(downloadItem.format.format_note)
|
||||
}.absolutePath
|
||||
)
|
||||
request.addOption("-P", tempFileDir.absolutePath)
|
||||
request.addOption("-P", pathUsed)
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -327,33 +345,43 @@ class DownloadWorker(
|
|||
}
|
||||
}
|
||||
}.onSuccess {
|
||||
//move file from internal to set download directory
|
||||
setProgressAsync(workDataOf("progress" to 100, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id, "log" to logDownloads))
|
||||
var finalPaths : List<String>?
|
||||
try {
|
||||
finalPaths = FileUtil.moveFile(tempFileDir.absoluteFile,context, downloadLocation, keepCache){ p ->
|
||||
setProgressAsync(workDataOf("progress" to p, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id, "log" to logDownloads))
|
||||
}
|
||||
if (finalPaths.isNotEmpty()){
|
||||
setProgressAsync(workDataOf("progress" to 100, "output" to "Moved file to $downloadLocation", "id" to downloadItem.id, "log" to logDownloads))
|
||||
}else{
|
||||
finalPaths = listOf(context.getString(R.string.unfound_file))
|
||||
}
|
||||
}catch (e: Exception){
|
||||
finalPaths = listOf(context.getString(R.string.unfound_file))
|
||||
e.printStackTrace()
|
||||
handler.postDelayed({
|
||||
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
val wasQuickDownloaded = updateDownloadItem(downloadItem, infoUtil, dao, resultDao)
|
||||
var finalPaths: List<String>
|
||||
if (useCacheFolder){
|
||||
//move file from internal to set download directory
|
||||
setProgressAsync(workDataOf("progress" to 100, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id, "log" to logDownloads))
|
||||
try {
|
||||
finalPaths = FileUtil.moveFile(tempFileDir.absoluteFile,context, downloadLocation, keepCache){ p ->
|
||||
setProgressAsync(workDataOf("progress" to p, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id, "log" to logDownloads))
|
||||
}
|
||||
if (finalPaths.isNotEmpty()){
|
||||
setProgressAsync(workDataOf("progress" to 100, "output" to "Moved file to $downloadLocation", "id" to downloadItem.id, "log" to logDownloads))
|
||||
}else{
|
||||
finalPaths = listOf(context.getString(R.string.unfound_file))
|
||||
}
|
||||
}catch (e: Exception){
|
||||
finalPaths = listOf(context.getString(R.string.unfound_file))
|
||||
e.printStackTrace()
|
||||
handler.postDelayed({
|
||||
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
|
||||
}, 1000)
|
||||
}
|
||||
}else{
|
||||
val now = System.currentTimeMillis()
|
||||
finalPaths = downloadLocationFile
|
||||
.walkTopDown()
|
||||
.filter { it.isFile && it.absolutePath.contains(downloadItem.title) && it.lastModified() > now - 10000000}
|
||||
.map { it.absolutePath }
|
||||
.toList()
|
||||
|
||||
if (finalPaths.isEmpty()) finalPaths = listOf(context.getString(R.string.unfound_file))
|
||||
}
|
||||
|
||||
//put download in history
|
||||
val incognito = sharedPreferences.getBoolean("incognito", false)
|
||||
if (!incognito) {
|
||||
val unixtime = System.currentTimeMillis() / 1000
|
||||
val file = File(finalPaths?.first()!!)
|
||||
val file = File(finalPaths.first()!!)
|
||||
downloadItem.format.filesize = file.length()
|
||||
val historyItem = HistoryItem(0, downloadItem.url, downloadItem.title, downloadItem.author, downloadItem.duration, downloadItem.thumb, downloadItem.type, unixtime, finalPaths.first() , downloadItem.website, downloadItem.format, downloadItem.id)
|
||||
runBlocking {
|
||||
|
|
@ -363,9 +391,7 @@ class DownloadWorker(
|
|||
notificationUtil.cancelDownloadNotification(downloadItem.id.toInt())
|
||||
|
||||
notificationUtil.createDownloadFinished(
|
||||
downloadItem.title, if (finalPaths?.first().equals(context.getString(R.string.unfound_file))) null else listOf(
|
||||
finalPaths!!.maxByOrNull { it.length }!!
|
||||
),
|
||||
downloadItem.title, if (finalPaths?.first().equals(context.getString(R.string.unfound_file))) null else finalPaths,
|
||||
NotificationUtil.DOWNLOAD_FINISHED_CHANNEL_ID
|
||||
)
|
||||
|
||||
|
|
|
|||
4
app/src/main/res/drawable/baseline_sort_24.xml
Normal file
4
app/src/main/res/drawable/baseline_sort_24.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<vector android:autoMirrored="true" android:height="24dp" android:viewportHeight="24"
|
||||
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="?android:colorAccent" android:pathData="M3,18h6v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h12v-2L3,11v2z"/>
|
||||
</vector>
|
||||
33
app/src/main/res/layout/fragment_inqueue.xml
Normal file
33
app/src/main/res/layout/fragment_inqueue.xml
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/filesize"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="30dp"
|
||||
android:paddingVertical="10dp"
|
||||
android:text="@string/file_size"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/download_recyclerview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/filesize">
|
||||
|
||||
</androidx.recyclerview.widget.RecyclerView>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -8,6 +8,13 @@
|
|||
android:title="@string/remove_results"
|
||||
android:icon="@drawable/baseline_delete_24"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/download"
|
||||
android:title="@string/download"
|
||||
android:icon="@drawable/baseline_download_24"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/select_all"
|
||||
android:title="@string/select_all"
|
||||
|
|
|
|||
|
|
@ -58,8 +58,20 @@
|
|||
</string-array>
|
||||
|
||||
<string-array name="audio_formats">
|
||||
<item>@string/worst_quality</item>
|
||||
<item>@string/best_quality</item>
|
||||
<item>worst</item>
|
||||
<item>best</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="format_ordering">
|
||||
<item>@string/file_size</item>
|
||||
<item>@string/container</item>
|
||||
<item>ID</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="format_ordering_values">
|
||||
<item>filesize</item>
|
||||
<item>container</item>
|
||||
<item>id</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="sponsorblock_settings_entries">
|
||||
|
|
|
|||
|
|
@ -279,4 +279,7 @@
|
|||
<string name="piped_instance">Piped Instance</string>
|
||||
<string name="piped_instance_summary">Write a PIPED API Server, the app can use for YouTube queries and formats</string>
|
||||
<string name="custom_audio_quality">Use Custom Audio Quality</string>
|
||||
<string name="retries">Retries</string>
|
||||
<string name="fragment_retries">Fragment Retries</string>
|
||||
<string name="format_order">Format Order</string>
|
||||
</resources>
|
||||
|
|
@ -104,6 +104,14 @@
|
|||
app:summary="@string/log_downloads_summary"
|
||||
app:title="@string/log_downloads" />
|
||||
|
||||
<EditTextPreference
|
||||
app:key="retries"
|
||||
app:title="@string/retries" />
|
||||
|
||||
<EditTextPreference
|
||||
app:key="fragment_retries"
|
||||
app:title="@string/fragment_retries" />
|
||||
|
||||
<Preference
|
||||
app:icon="@drawable/ic_battery"
|
||||
app:key="ignore_battery"
|
||||
|
|
|
|||
|
|
@ -38,6 +38,15 @@
|
|||
app:useSimpleSummaryProvider="true"
|
||||
app:title="@string/format_source" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="filesize"
|
||||
android:entries="@array/format_ordering"
|
||||
android:entryValues="@array/format_ordering_values"
|
||||
android:icon="@drawable/baseline_sort_24"
|
||||
app:key="format_order"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:title="@string/format_order" />
|
||||
|
||||
<EditTextPreference
|
||||
app:key="piped_instance"
|
||||
app:defaultValue="https://pipedapi.kavin.rocks"
|
||||
|
|
|
|||
Loading…
Reference in a new issue