stuff
This commit is contained in:
parent
025ec65c27
commit
6b89d3e3fd
43 changed files with 871 additions and 384 deletions
|
|
@ -10,7 +10,7 @@ def properties = new Properties()
|
||||||
def versionMajor = 1
|
def versionMajor = 1
|
||||||
def versionMinor = 6
|
def versionMinor = 6
|
||||||
def versionPatch = 5
|
def versionPatch = 5
|
||||||
def versionBuild = 2 // bump for dogfood builds, public betas, etc.
|
def versionBuild = 4 // bump for dogfood builds, public betas, etc.
|
||||||
def versionExt = ""
|
def versionExt = ""
|
||||||
|
|
||||||
if (versionBuild > 0){
|
if (versionBuild > 0){
|
||||||
|
|
@ -63,6 +63,7 @@ android {
|
||||||
release {
|
release {
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
shrinkResources true
|
shrinkResources true
|
||||||
|
manifestPlaceholders = [appName: RELEASE_NAME]
|
||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
debuggable false
|
debuggable false
|
||||||
signingConfig signingConfigs.debug
|
signingConfig signingConfigs.debug
|
||||||
|
|
@ -71,6 +72,7 @@ android {
|
||||||
beta {
|
beta {
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
shrinkResources true
|
shrinkResources true
|
||||||
|
manifestPlaceholders = [appName: BETA_NAME]
|
||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
debuggable false
|
debuggable false
|
||||||
signingConfig signingConfigs.debug
|
signingConfig signingConfigs.debug
|
||||||
|
|
@ -80,6 +82,7 @@ android {
|
||||||
|
|
||||||
debug {
|
debug {
|
||||||
minifyEnabled false
|
minifyEnabled false
|
||||||
|
manifestPlaceholders = [appName: RELEASE_NAME]
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
debuggable true
|
debuggable true
|
||||||
signingConfig signingConfigs.debug
|
signingConfig signingConfigs.debug
|
||||||
|
|
@ -87,6 +90,7 @@ android {
|
||||||
|
|
||||||
benchmark {
|
benchmark {
|
||||||
initWith release
|
initWith release
|
||||||
|
manifestPlaceholders = [appName: RELEASE_NAME]
|
||||||
proguardFiles 'baseline-profiles-rules.pro'
|
proguardFiles 'baseline-profiles-rules.pro'
|
||||||
debuggable false
|
debuggable false
|
||||||
matchingFallbacks ['release']
|
matchingFallbacks ['release']
|
||||||
|
|
|
||||||
|
|
@ -15,16 +15,20 @@
|
||||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
|
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
|
||||||
|
android:maxSdkVersion="32"/>
|
||||||
|
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
|
||||||
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".App"
|
android:name=".App"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
android:configChanges="layoutDirection|locale"
|
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|locale"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:enableOnBackInvokedCallback="true"
|
android:enableOnBackInvokedCallback="true"
|
||||||
android:extractNativeLibs="true"
|
android:extractNativeLibs="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="${appName}"
|
||||||
android:localeConfig="@xml/locales_config"
|
android:localeConfig="@xml/locales_config"
|
||||||
android:requestLegacyExternalStorage="true"
|
android:requestLegacyExternalStorage="true"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
|
@ -38,14 +42,14 @@
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
|
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|locale"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:windowSoftInputMode="adjustPan">
|
android:windowSoftInputMode="adjustPan">
|
||||||
|
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".receiver.ShareActivity"
|
android:name=".receiver.ShareActivity"
|
||||||
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
|
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|locale"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:launchMode="singleInstance"
|
android:launchMode="singleInstance"
|
||||||
|
|
@ -71,7 +75,7 @@
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".receiver.ShareFileActivity"
|
android:name=".receiver.ShareFileActivity"
|
||||||
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
|
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|locale"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:launchMode="singleInstance"
|
android:launchMode="singleInstance"
|
||||||
|
|
@ -92,7 +96,7 @@
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.more.TerminalActivity"
|
android:name=".ui.more.TerminalActivity"
|
||||||
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
|
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|locale"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:label="@string/terminal"
|
android:label="@string/terminal"
|
||||||
android:parentActivityName=".MainActivity">
|
android:parentActivityName=".MainActivity">
|
||||||
|
|
@ -122,7 +126,7 @@
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
|
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|locale"
|
||||||
android:targetActivity=".MainActivity"
|
android:targetActivity=".MainActivity"
|
||||||
android:windowSoftInputMode="adjustPan"
|
android:windowSoftInputMode="adjustPan"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
|
|
@ -144,7 +148,7 @@
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:icon="@mipmap/ic_launcher_dark"
|
android:icon="@mipmap/ic_launcher_dark"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round_dark"
|
android:roundIcon="@mipmap/ic_launcher_round_dark"
|
||||||
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
|
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|locale"
|
||||||
android:targetActivity=".MainActivity"
|
android:targetActivity=".MainActivity"
|
||||||
android:windowSoftInputMode="adjustPan">
|
android:windowSoftInputMode="adjustPan">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|
@ -164,7 +168,7 @@
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:icon="@mipmap/ic_launcher_light"
|
android:icon="@mipmap/ic_launcher_light"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round_light"
|
android:roundIcon="@mipmap/ic_launcher_round_light"
|
||||||
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
|
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|locale"
|
||||||
android:targetActivity=".MainActivity"
|
android:targetActivity=".MainActivity"
|
||||||
android:windowSoftInputMode="adjustPan">
|
android:windowSoftInputMode="adjustPan">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|
@ -180,7 +184,7 @@
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.more.WebViewActivity"
|
android:name=".ui.more.WebViewActivity"
|
||||||
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
|
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|locale"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:parentActivityName=".MainActivity"
|
android:parentActivityName=".MainActivity"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
|
@ -193,10 +197,12 @@
|
||||||
|
|
||||||
<receiver android:name=".receiver.CancelDownloadNotificationReceiver" />
|
<receiver android:name=".receiver.CancelDownloadNotificationReceiver" />
|
||||||
<receiver android:name=".receiver.PauseDownloadNotificationReceiver" />
|
<receiver android:name=".receiver.PauseDownloadNotificationReceiver" />
|
||||||
|
<receiver android:name=".receiver.AlarmStartReceiver" />
|
||||||
|
<receiver android:name=".receiver.AlarmStopReceiver" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".receiver.ResumeActivity"
|
android:name=".receiver.ResumeActivity"
|
||||||
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
|
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|locale"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:launchMode="singleInstance"
|
android:launchMode="singleInstance"
|
||||||
|
|
|
||||||
|
|
@ -12,33 +12,25 @@ import android.graphics.drawable.ColorDrawable
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.view.WindowInsets
|
import android.view.WindowInsets
|
||||||
import android.widget.CheckBox
|
import android.widget.CheckBox
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.view.ViewCompat
|
|
||||||
import androidx.core.view.WindowCompat
|
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.forEach
|
import androidx.core.view.forEach
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.core.view.updatePadding
|
|
||||||
import androidx.fragment.app.FragmentContainerView
|
import androidx.fragment.app.FragmentContainerView
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import androidx.navigation.NavDirections
|
|
||||||
import androidx.navigation.fragment.FragmentNavigatorExtras
|
|
||||||
import androidx.navigation.fragment.NavHostFragment
|
import androidx.navigation.fragment.NavHostFragment
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.navigation.navOptions
|
|
||||||
import androidx.navigation.ui.setupWithNavController
|
import androidx.navigation.ui.setupWithNavController
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
|
|
@ -60,7 +52,6 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
import java.io.InputStreamReader
|
import java.io.InputStreamReader
|
||||||
import java.io.Reader
|
import java.io.Reader
|
||||||
|
|
@ -157,7 +148,6 @@ class MainActivity : BaseActivity() {
|
||||||
activeDownloadsBadge.number = it
|
activeDownloadsBadge.number = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
window.navigationBarColor = SurfaceColors.SURFACE_2.getColor(this)
|
|
||||||
}
|
}
|
||||||
if (navigationView is NavigationView){
|
if (navigationView is NavigationView){
|
||||||
(navigationView as NavigationView).setupWithNavController(navController)
|
(navigationView as NavigationView).setupWithNavController(navController)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@ import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Calendar
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
|
||||||
class Converters {
|
class Converters {
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,15 @@ import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
interface LogDao {
|
interface LogDao {
|
||||||
@Query("SELECT * FROM logs ORDER BY id DESC")
|
@Query("SELECT id, title, downloadType, format, downloadTime, '' as content FROM logs ORDER BY id DESC")
|
||||||
fun getAllLogs() : List<LogItem>
|
fun getAllLogs() : List<LogItem>
|
||||||
|
|
||||||
@Query("SELECT * FROM logs ORDER BY id DESC")
|
@Query("SELECT id, title, downloadType, format, downloadTime, '' as content FROM logs ORDER BY id DESC")
|
||||||
fun getAllLogsFlow() : Flow<List<LogItem>>
|
fun getAllLogsFlow() : Flow<List<LogItem>>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM logs WHERE id=:id LIMIT 1")
|
||||||
|
fun getLogFlowByID(id: Long) : Flow<LogItem>
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
suspend fun insert(item: LogItem) : Long
|
suspend fun insert(item: LogItem) : Long
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,10 @@ class LogRepository(private val logDao: LogDao) {
|
||||||
return logDao.getAllLogs()
|
return logDao.getAllLogs()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getLogFlowByID(id: Long) : Flow<LogItem> {
|
||||||
|
return logDao.getLogFlowByID(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
suspend fun insert(item: LogItem) : Long{
|
suspend fun insert(item: LogItem) : Long{
|
||||||
return logDao.insert(item)
|
return logDao.insert(item)
|
||||||
|
|
|
||||||
|
|
@ -38,14 +38,19 @@ 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.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.DownloadWorker
|
import com.deniscerri.ytdlnis.work.DownloadWorker
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Calendar
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
|
@ -554,10 +559,17 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
|
|
||||||
suspend fun queueDownloads(items: List<DownloadItem>) = CoroutineScope(Dispatchers.IO).launch {
|
suspend fun queueDownloads(items: List<DownloadItem>) = CoroutineScope(Dispatchers.IO).launch {
|
||||||
val context = App.instance
|
val context = App.instance
|
||||||
|
val alarmScheduler = AlarmScheduler(context)
|
||||||
val activeAndQueuedDownloads = repository.getActiveAndQueuedDownloads()
|
val activeAndQueuedDownloads = repository.getActiveAndQueuedDownloads()
|
||||||
val queuedItems = mutableListOf<DownloadItem>()
|
val queuedItems = mutableListOf<DownloadItem>()
|
||||||
var exists = false
|
var exists = false
|
||||||
|
|
||||||
|
//if scheduler is on
|
||||||
|
val useScheduler = sharedPreferences.getBoolean("use_scheduler", false)
|
||||||
|
if (useScheduler && !alarmScheduler.isDuringTheScheduledTime()){
|
||||||
|
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()
|
||||||
if (activeAndQueuedDownloads.firstOrNull{d ->
|
if (activeAndQueuedDownloads.firstOrNull{d ->
|
||||||
|
|
@ -586,7 +598,23 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startDownloadWorker(queuedItems)
|
if (!useScheduler || alarmScheduler.isDuringTheScheduledTime() || items.any { it.downloadStartTime > 0L } ){
|
||||||
|
startDownloadWorker(queuedItems)
|
||||||
|
|
||||||
|
if(!useScheduler){
|
||||||
|
queuedItems.filter { it.downloadStartTime != 0L && (it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty()) }.forEach {
|
||||||
|
try{
|
||||||
|
updateDownloadItem(it, infoUtil, dbManager.downloadDao, dbManager.resultDao)
|
||||||
|
}catch (ignored: Exception){}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
queuedItems.filter { it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty() }.forEach {
|
||||||
|
try{
|
||||||
|
updateDownloadItem(it, infoUtil, dbManager.downloadDao, dbManager.resultDao)
|
||||||
|
}catch (ignored: Exception){}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun startDownloadWorker(queuedItems: List<DownloadItem>) {
|
private suspend fun startDownloadWorker(queuedItems: List<DownloadItem>) {
|
||||||
|
|
@ -598,12 +626,12 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
if (currentWork.size == 0 || currentWork.none{ it.state == WorkInfo.State.RUNNING } || (queuedItems.isNotEmpty() && queuedItems[0].downloadStartTime != 0L)){
|
if (currentWork.size == 0 || currentWork.none{ it.state == WorkInfo.State.RUNNING } || (queuedItems.isNotEmpty() && queuedItems[0].downloadStartTime != 0L)){
|
||||||
|
|
||||||
val currentTime = System.currentTimeMillis()
|
val currentTime = System.currentTimeMillis()
|
||||||
var delay = 1000L
|
var delay = 0L
|
||||||
if (queuedItems.isNotEmpty()){
|
if (queuedItems.isNotEmpty()){
|
||||||
delay = if (queuedItems[0].downloadStartTime != 0L){
|
delay = if (queuedItems[0].downloadStartTime != 0L){
|
||||||
queuedItems[0].downloadStartTime.minus(currentTime)
|
queuedItems[0].downloadStartTime.minus(currentTime)
|
||||||
} else 0
|
} else 0
|
||||||
if (delay <= 0L) delay = 1000L
|
if (delay <= 60000L) delay = 0L
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -635,12 +663,6 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
Toast.makeText(context, context.getString(R.string.metered_network_download_start_info), Toast.LENGTH_LONG).show()
|
Toast.makeText(context, context.getString(R.string.metered_network_download_start_info), Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
queuedItems.filter { it.downloadStartTime != 0L && (it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty()) }?.forEach {
|
|
||||||
try{
|
|
||||||
updateDownloadItem(it, infoUtil, dbManager.downloadDao, dbManager.resultDao)
|
|
||||||
}catch (ignored: Exception){}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getQueuedCollectedFileSize() : Long {
|
fun getQueuedCollectedFileSize() : Long {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.deniscerri.ytdlnis.database.viewmodel
|
package com.deniscerri.ytdlnis.database.viewmodel
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.util.Log
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.asLiveData
|
import androidx.lifecycle.asLiveData
|
||||||
|
|
@ -10,6 +11,7 @@ import com.deniscerri.ytdlnis.database.models.LogItem
|
||||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
import com.deniscerri.ytdlnis.database.repository.LogRepository
|
import com.deniscerri.ytdlnis.database.repository.LogRepository
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class LogViewModel(private val application: Application) : AndroidViewModel(application) {
|
class LogViewModel(private val application: Application) : AndroidViewModel(application) {
|
||||||
|
|
@ -24,6 +26,10 @@ class LogViewModel(private val application: Application) : AndroidViewModel(appl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun getLogFlowByID(id: Long) : LiveData<LogItem> {
|
||||||
|
return repository.getLogFlowByID(id).asLiveData()
|
||||||
|
}
|
||||||
|
|
||||||
fun getItemById(id: Long): LogItem{
|
fun getItemById(id: Long): LogItem{
|
||||||
return repository.getItem(id)
|
return repository.getItem(id)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package com.deniscerri.ytdlnis.receiver
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.work.Constraints
|
||||||
|
import androidx.work.ExistingWorkPolicy
|
||||||
|
import androidx.work.OneTimeWorkRequestBuilder
|
||||||
|
import androidx.work.WorkManager
|
||||||
|
import com.deniscerri.ytdlnis.work.DownloadWorker
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
class AlarmStartReceiver : BroadcastReceiver() {
|
||||||
|
override fun onReceive(p0: Context?, p1: Intent?) {
|
||||||
|
Log.e("assa", "Start")
|
||||||
|
val workConstraints = Constraints.Builder()
|
||||||
|
val workRequest = OneTimeWorkRequestBuilder<DownloadWorker>()
|
||||||
|
.addTag("download")
|
||||||
|
.setConstraints(workConstraints.build())
|
||||||
|
.setInitialDelay(1000L, TimeUnit.MILLISECONDS)
|
||||||
|
|
||||||
|
WorkManager.getInstance(p0!!).enqueueUniqueWork(
|
||||||
|
System.currentTimeMillis().toString(),
|
||||||
|
ExistingWorkPolicy.REPLACE,
|
||||||
|
workRequest.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
package com.deniscerri.ytdlnis.receiver
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import com.deniscerri.ytdlnis.database.DBManager
|
||||||
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
|
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||||
|
import com.deniscerri.ytdlnis.work.AlarmScheduler
|
||||||
|
import com.yausername.youtubedl_android.YoutubeDL
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Calendar
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class AlarmStopReceiver : BroadcastReceiver() {
|
||||||
|
override fun onReceive(p0: Context?, p1: Intent?) {
|
||||||
|
val dbManager = DBManager.getInstance(p0!!)
|
||||||
|
val ytdl = YoutubeDL.getInstance()
|
||||||
|
val notificationUtil = NotificationUtil(p0)
|
||||||
|
val preferences = PreferenceManager.getDefaultSharedPreferences(p0)
|
||||||
|
val alarmScheduler = AlarmScheduler(p0)
|
||||||
|
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
runCatching {
|
||||||
|
Log.e("assa", "assssssssssssssssssssssssssssss")
|
||||||
|
val active = dbManager.downloadDao.getActiveDownloadsList()
|
||||||
|
|
||||||
|
val startingTime = preferences.getString("schedule_start", "00:00")!!
|
||||||
|
val sTime = Calendar.getInstance()
|
||||||
|
sTime.set(Calendar.HOUR_OF_DAY, startingTime.split(":")[0].toInt())
|
||||||
|
sTime.set(Calendar.MINUTE, startingTime.split(":")[1].toInt())
|
||||||
|
sTime.set(Calendar.SECOND, 0)
|
||||||
|
val time = alarmScheduler.calculateNextTime(sTime)
|
||||||
|
|
||||||
|
active.forEach {
|
||||||
|
ytdl.destroyProcessById(it.id.toString())
|
||||||
|
notificationUtil.cancelDownloadNotification(it.id.toInt())
|
||||||
|
it.status = DownloadRepository.Status.Queued.toString()
|
||||||
|
dbManager.downloadDao.update(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
AlarmScheduler(p0).schedule()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -114,6 +114,7 @@ class ShareActivity : BaseActivity() {
|
||||||
loadingBottomSheet.show()
|
loadingBottomSheet.show()
|
||||||
val cancel = loadingBottomSheet.findViewById<TextView>(R.id.cancel)
|
val cancel = loadingBottomSheet.findViewById<TextView>(R.id.cancel)
|
||||||
cancel!!.setOnClickListener {
|
cancel!!.setOnClickListener {
|
||||||
|
resultViewModel.deleteAll()
|
||||||
this.finish()
|
this.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,12 @@ import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.util.ThemeUtil
|
import com.deniscerri.ytdlnis.util.ThemeUtil
|
||||||
|
import com.google.android.material.elevation.SurfaceColors
|
||||||
|
|
||||||
open class BaseActivity : AppCompatActivity() {
|
open class BaseActivity : AppCompatActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
ThemeUtil.updateTheme(this)
|
ThemeUtil.updateTheme(this)
|
||||||
|
window.navigationBarColor = SurfaceColors.SURFACE_2.getColor(this)
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -28,7 +28,9 @@ import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||||
import com.google.android.material.tabs.TabLayout
|
import com.google.android.material.tabs.TabLayout
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
|
@ -78,7 +80,10 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
|
||||||
overScrollMode = View.OVER_SCROLL_NEVER
|
overScrollMode = View.OVER_SCROLL_NEVER
|
||||||
}
|
}
|
||||||
|
|
||||||
val fragments = mutableListOf(DownloadAudioFragment(resultItem, downloadItem), DownloadVideoFragment(resultItem, downloadItem))
|
val fragments = mutableListOf(
|
||||||
|
DownloadAudioFragment(resultItem, downloadItem),
|
||||||
|
DownloadVideoFragment(resultItem, downloadItem)
|
||||||
|
)
|
||||||
var commandTemplateNr = 0
|
var commandTemplateNr = 0
|
||||||
lifecycleScope.launch{
|
lifecycleScope.launch{
|
||||||
withContext(Dispatchers.IO){
|
withContext(Dispatchers.IO){
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,18 @@
|
||||||
package com.deniscerri.ytdlnis.ui.downloadcard
|
package com.deniscerri.ytdlnis.ui.downloadcard
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
import android.util.DisplayMetrics
|
|
||||||
import android.view.Gravity
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.Window
|
|
||||||
import android.view.inputmethod.InputMethodManager
|
|
||||||
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.Button
|
|
||||||
import android.widget.EditText
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.core.widget.doOnTextChanged
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
|
@ -36,21 +24,15 @@ import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
||||||
import com.deniscerri.ytdlnis.databinding.ExtraCommandsBottomSheetBinding
|
|
||||||
import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding
|
import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
|
||||||
import com.google.android.material.button.MaterialButton
|
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
import com.google.android.material.chip.Chip
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ 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.tabs.TabLayout
|
import com.google.android.material.tabs.TabLayout
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
@ -80,7 +81,6 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
tabLayout = view.findViewById(R.id.download_tablayout)
|
tabLayout = view.findViewById(R.id.download_tablayout)
|
||||||
viewPager2 = view.findViewById(R.id.download_viewpager)
|
viewPager2 = view.findViewById(R.id.download_viewpager)
|
||||||
|
|
||||||
|
|
@ -88,6 +88,7 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
|
||||||
isNestedScrollingEnabled = false
|
isNestedScrollingEnabled = false
|
||||||
overScrollMode = View.OVER_SCROLL_NEVER
|
overScrollMode = View.OVER_SCROLL_NEVER
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadAudioFragment = DownloadAudioFragment(resultItem, downloadItem)
|
downloadAudioFragment = DownloadAudioFragment(resultItem, downloadItem)
|
||||||
downloadVideoFragment = DownloadVideoFragment(resultItem, downloadItem)
|
downloadVideoFragment = DownloadVideoFragment(resultItem, downloadItem)
|
||||||
val fragments = mutableListOf(downloadAudioFragment, downloadVideoFragment)
|
val fragments = mutableListOf(downloadAudioFragment, downloadVideoFragment)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
|
@ -185,8 +186,7 @@ class DownloadCommandFragment(private val resultItem: ResultItem, private var cu
|
||||||
},
|
},
|
||||||
editSelectedClicked =
|
editSelectedClicked =
|
||||||
{
|
{
|
||||||
var current = templates.find { it.id == commandTemplateCard.findViewById<android.widget.TextView>(
|
var current = templates.find { it.id == commandTemplateCard.tag }
|
||||||
R.id.id).toString().toLong() }
|
|
||||||
if (current == null) current =
|
if (current == null) current =
|
||||||
CommandTemplate(
|
CommandTemplate(
|
||||||
0,
|
0,
|
||||||
|
|
@ -197,7 +197,8 @@ class DownloadCommandFragment(private val resultItem: ResultItem, private var cu
|
||||||
UiUtil.showCommandTemplateCreationOrUpdatingSheet(current, requireActivity(), viewLifecycleOwner, commandTemplateViewModel) {
|
UiUtil.showCommandTemplateCreationOrUpdatingSheet(current, requireActivity(), viewLifecycleOwner, commandTemplateViewModel) {
|
||||||
templates.add(it)
|
templates.add(it)
|
||||||
chosenCommandView.editText!!.setText(it.content)
|
chosenCommandView.editText!!.setText(it.content)
|
||||||
downloadItem.format = com.deniscerri.ytdlnis.database.models.Format(
|
populateCommandCard(commandTemplateCard, it)
|
||||||
|
downloadItem.format = Format(
|
||||||
it.title,
|
it.title,
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
|
|
|
||||||
|
|
@ -2,31 +2,18 @@ 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.Context
|
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.icu.text.IDNA.Info
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
import android.util.DisplayMetrics
|
|
||||||
import android.view.Gravity
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.Window
|
|
||||||
import android.view.inputmethod.InputMethodManager
|
|
||||||
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.Button
|
|
||||||
import android.widget.EditText
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.core.widget.addTextChangedListener
|
|
||||||
import androidx.core.widget.doOnTextChanged
|
|
||||||
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
|
||||||
|
|
@ -42,10 +29,7 @@ import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
import com.google.android.material.chip.Chip
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.work.WorkInfo
|
import androidx.work.WorkInfo
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
|
import androidx.work.WorkQuery
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.adapter.ActiveDownloadAdapter
|
import com.deniscerri.ytdlnis.adapter.ActiveDownloadAdapter
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
|
|
@ -131,7 +132,7 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
||||||
}
|
}
|
||||||
|
|
||||||
WorkManager.getInstance(requireContext())
|
WorkManager.getInstance(requireContext())
|
||||||
.getWorkInfosByTagLiveData("download")
|
.getWorkInfosLiveData(WorkQuery.fromStates(WorkInfo.State.RUNNING))
|
||||||
.observe(viewLifecycleOwner){ list ->
|
.observe(viewLifecycleOwner){ list ->
|
||||||
list.forEach {work ->
|
list.forEach {work ->
|
||||||
if (work == null) return@forEach
|
if (work == null) return@forEach
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import android.graphics.Color
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.FileObserver
|
import android.os.FileObserver
|
||||||
|
import android.os.PersistableBundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
|
@ -76,7 +77,7 @@ class TerminalActivity : BaseActivity() {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_terminal)
|
setContentView(R.layout.activity_terminal)
|
||||||
|
|
||||||
downloadID = System.currentTimeMillis().toInt() % 100000
|
downloadID = savedInstanceState?.getInt("downloadID") ?: (System.currentTimeMillis().toInt() % 100000)
|
||||||
downloadFile = File(cacheDir.absolutePath + "/$downloadID.txt")
|
downloadFile = File(cacheDir.absolutePath + "/$downloadID.txt")
|
||||||
if (! downloadFile.exists()) downloadFile.createNewFile()
|
if (! downloadFile.exists()) downloadFile.createNewFile()
|
||||||
imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
|
@ -233,6 +234,22 @@ class TerminalActivity : BaseActivity() {
|
||||||
|
|
||||||
input?.enableTextHighlight()
|
input?.enableTextHighlight()
|
||||||
output?.enableTextHighlight()
|
output?.enableTextHighlight()
|
||||||
|
|
||||||
|
input?.setText(savedInstanceState?.getString("input") ?: "yt-dlp ")
|
||||||
|
input!!.requestFocus()
|
||||||
|
input!!.setSelection(input!!.text.length)
|
||||||
|
output?.text = savedInstanceState?.getString("output") ?: ""
|
||||||
|
if (savedInstanceState?.getBoolean("run") == true){
|
||||||
|
showCancelFab()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle, outPersistentState: PersistableBundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
outState.putString("input", input?.text.toString())
|
||||||
|
outState.putString("output", output?.text.toString())
|
||||||
|
outState.putBoolean("run", fab!!.text == getString(R.string.run_command))
|
||||||
|
outState.putInt("downloadID", downloadID)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initMenu() {
|
private fun initMenu() {
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,11 @@ 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.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
|
import kotlin.math.log
|
||||||
|
|
||||||
|
|
||||||
class DownloadLogFragment : Fragment() {
|
class DownloadLogFragment : Fragment() {
|
||||||
|
|
@ -78,7 +80,7 @@ class DownloadLogFragment : Fragment() {
|
||||||
|
|
||||||
|
|
||||||
logViewModel = ViewModelProvider(this)[LogViewModel::class.java]
|
logViewModel = ViewModelProvider(this)[LogViewModel::class.java]
|
||||||
logViewModel.items.map { it.find { log -> log.id == id } }.observe(viewLifecycleOwner) { logItem ->
|
logViewModel.getLogFlowByID(id!!).observe(viewLifecycleOwner){logItem ->
|
||||||
mainActivity.runOnUiThread{
|
mainActivity.runOnUiThread{
|
||||||
content.text = logItem?.content
|
content.text = logItem?.content
|
||||||
content.scrollTo(0, content.height)
|
content.scrollTo(0, content.height)
|
||||||
|
|
@ -86,9 +88,8 @@ class DownloadLogFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val logItem = logViewModel.getItemById(id!!)
|
val logItem = logViewModel.getItemById(id)
|
||||||
topAppBar.title = logItem.title
|
topAppBar.title = logItem.title
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,12 +26,15 @@ import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.adapter.DownloadLogsAdapter
|
import com.deniscerri.ytdlnis.adapter.DownloadLogsAdapter
|
||||||
import com.deniscerri.ytdlnis.database.models.LogItem
|
import com.deniscerri.ytdlnis.database.models.LogItem
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.LogViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.LogViewModel
|
||||||
|
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
|
||||||
|
import com.deniscerri.ytdlnis.util.UiUtil.forceFastScrollMode
|
||||||
import com.google.android.material.appbar.MaterialToolbar
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
|
||||||
class DownloadLogListFragment : Fragment(), DownloadLogsAdapter.OnItemClickListener {
|
class DownloadLogListFragment : Fragment(), DownloadLogsAdapter.OnItemClickListener {
|
||||||
|
|
@ -69,6 +72,8 @@ class DownloadLogListFragment : Fragment(), DownloadLogsAdapter.OnItemClickListe
|
||||||
)
|
)
|
||||||
recyclerView = view.findViewById(R.id.logs_recyclerview)
|
recyclerView = view.findViewById(R.id.logs_recyclerview)
|
||||||
recyclerView.layoutManager = LinearLayoutManager(context)
|
recyclerView.layoutManager = LinearLayoutManager(context)
|
||||||
|
recyclerView.enableFastScroll()
|
||||||
|
recyclerView.forceFastScrollMode()
|
||||||
recyclerView.adapter = downloadLogAdapter
|
recyclerView.adapter = downloadLogAdapter
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
if (preferences.getBoolean("swipe_gestures", true)){
|
if (preferences.getBoolean("swipe_gestures", true)){
|
||||||
|
|
@ -230,14 +235,18 @@ class DownloadLogListFragment : Fragment(), DownloadLogsAdapter.OnItemClickListe
|
||||||
val position = viewHolder.bindingAdapterPosition
|
val position = viewHolder.bindingAdapterPosition
|
||||||
when (direction) {
|
when (direction) {
|
||||||
ItemTouchHelper.LEFT -> {
|
ItemTouchHelper.LEFT -> {
|
||||||
val deletedItem = items[position]
|
lifecycleScope.launch {
|
||||||
logViewModel.delete(deletedItem)
|
val deletedItem = withContext(Dispatchers.IO){
|
||||||
Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
|
logViewModel.getItemById(items[position].id)
|
||||||
.setAction(getString(R.string.undo)) {
|
}
|
||||||
lifecycleScope.launch(Dispatchers.IO){
|
logViewModel.delete(deletedItem)
|
||||||
logViewModel.insert(deletedItem)
|
Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
|
||||||
}
|
.setAction(getString(R.string.undo)) {
|
||||||
}.show()
|
lifecycleScope.launch(Dispatchers.IO){
|
||||||
|
logViewModel.insert(deletedItem)
|
||||||
|
}
|
||||||
|
}.show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
package com.deniscerri.ytdlnis.ui.more.settings
|
package com.deniscerri.ytdlnis.ui.more.settings
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.content.DialogInterface
|
||||||
import android.view.View
|
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import android.widget.ListView
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.preference.EditTextPreference
|
import androidx.preference.EditTextPreference
|
||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
|
import androidx.preference.MultiSelectListPreference
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
|
|
@ -46,6 +45,24 @@ abstract class BaseSettingsFragment : PreferenceFragmentCompat() {
|
||||||
.setNegativeButton(R.string.cancel, null)
|
.setNegativeButton(R.string.cancel, null)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
is MultiSelectListPreference -> {
|
||||||
|
val selectedItems = preference.entryValues.map {
|
||||||
|
preference.values.contains(it)
|
||||||
|
}.toBooleanArray()
|
||||||
|
MaterialAlertDialogBuilder(requireContext())
|
||||||
|
.setTitle(preference.title)
|
||||||
|
.setMultiChoiceItems(preference.entries, selectedItems) { _, _, _ ->
|
||||||
|
val newValues = preference.entryValues
|
||||||
|
.filterIndexed { index, _ -> selectedItems[index] }
|
||||||
|
.map { it.toString() }
|
||||||
|
.toMutableSet()
|
||||||
|
if (preference.callChangeListener(newValues)) {
|
||||||
|
preference.values = newValues
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
is EditTextPreference -> {
|
is EditTextPreference -> {
|
||||||
val binding = TextinputBinding.inflate(layoutInflater)
|
val binding = TextinputBinding.inflate(layoutInflater)
|
||||||
binding.urlEdittext.setText(preference.text)
|
binding.urlEdittext.setText(preference.text)
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,70 @@
|
||||||
package com.deniscerri.ytdlnis.ui.more.settings
|
package com.deniscerri.ytdlnis.ui.more.settings
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.PendingIntent
|
||||||
import android.content.Context.POWER_SERVICE
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.PowerManager
|
|
||||||
import android.provider.Settings
|
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.SeekBarPreference
|
import androidx.preference.PreferenceManager
|
||||||
import com.deniscerri.ytdlnis.MainActivity
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.deniscerri.ytdlnis.receiver.AlarmStartReceiver
|
||||||
|
import com.deniscerri.ytdlnis.receiver.AlarmStopReceiver
|
||||||
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
|
import com.deniscerri.ytdlnis.work.AlarmScheduler
|
||||||
|
import java.util.Calendar
|
||||||
|
|
||||||
|
|
||||||
class DownloadSettingsFragment : BaseSettingsFragment() {
|
class DownloadSettingsFragment : BaseSettingsFragment() {
|
||||||
override val title: Int = R.string.downloads
|
override val title: Int = R.string.downloads
|
||||||
|
|
||||||
private var concurrentDownloads: SeekBarPreference? = null
|
|
||||||
private var ignoreBatteryOptimization: Preference? = null
|
|
||||||
|
|
||||||
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
setPreferencesFromResource(R.xml.downloading_preferences, rootKey)
|
setPreferencesFromResource(R.xml.downloading_preferences, rootKey)
|
||||||
|
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
|
|
||||||
ignoreBatteryOptimization = findPreference("ignore_battery")
|
val useScheduler = findPreference<SwitchPreferenceCompat>("use_scheduler")
|
||||||
val packageName: String = requireContext().packageName
|
val scheduleStart = findPreference<Preference>("schedule_start")
|
||||||
val pm = requireContext().applicationContext.getSystemService(POWER_SERVICE) as PowerManager
|
scheduleStart?.summary = preferences.getString("schedule_start", "00:00")
|
||||||
if (pm.isIgnoringBatteryOptimizations(packageName)) {
|
val scheduleEnd = findPreference<Preference>("schedule_end")
|
||||||
ignoreBatteryOptimization!!.isVisible = false
|
scheduleEnd?.summary = preferences.getString("schedule_end", "05:00")
|
||||||
|
val scheduler = AlarmScheduler(requireContext())
|
||||||
|
|
||||||
|
useScheduler?.setOnPreferenceChangeListener { preference, newValue ->
|
||||||
|
if (newValue as Boolean){
|
||||||
|
scheduler.schedule()
|
||||||
|
}else{
|
||||||
|
scheduler.cancel()
|
||||||
|
//start worker if there are leftover downloads waiting for scheduler
|
||||||
|
val intent = Intent(context, AlarmStartReceiver::class.java)
|
||||||
|
requireActivity().sendBroadcast(intent)
|
||||||
|
}
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
ignoreBatteryOptimization = findPreference("ignore_battery")
|
scheduleStart?.setOnPreferenceClickListener {
|
||||||
ignoreBatteryOptimization!!.onPreferenceClickListener =
|
UiUtil.showTimePicker(parentFragmentManager){
|
||||||
Preference.OnPreferenceClickListener {
|
val hr = it.get(Calendar.HOUR_OF_DAY)
|
||||||
val intent = Intent()
|
val mn = it.get(Calendar.MINUTE)
|
||||||
intent.action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
|
val formattedTime = String.format("%02d", hr) + ":" + String.format("%02d", mn)
|
||||||
intent.data = Uri.parse("package:" + requireContext().packageName)
|
preferences.edit().putString("schedule_start",formattedTime).apply()
|
||||||
startActivity(intent)
|
scheduleStart.summary = formattedTime
|
||||||
true
|
|
||||||
|
scheduler.schedule()
|
||||||
}
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduleEnd?.setOnPreferenceClickListener {
|
||||||
|
UiUtil.showTimePicker(parentFragmentManager){
|
||||||
|
val hr = it.get(Calendar.HOUR_OF_DAY)
|
||||||
|
val mn = it.get(Calendar.MINUTE)
|
||||||
|
val formattedTime = String.format("%02d", hr) + ":" + String.format("%02d", mn)
|
||||||
|
preferences.edit().putString("schedule_end",formattedTime).apply()
|
||||||
|
scheduleEnd.summary = formattedTime
|
||||||
|
|
||||||
|
scheduler.schedule()
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -29,6 +29,7 @@ class FolderSettingsFragment : BaseSettingsFragment() {
|
||||||
private var videoPath: Preference? = null
|
private var videoPath: Preference? = null
|
||||||
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 audioFilenameTemplate : EditTextPreference? = null
|
private var audioFilenameTemplate : EditTextPreference? = null
|
||||||
private var videoFilenameTemplate : EditTextPreference? = null
|
private var videoFilenameTemplate : EditTextPreference? = null
|
||||||
private var clearCache: Preference? = null
|
private var clearCache: Preference? = null
|
||||||
|
|
@ -46,6 +47,7 @@ class FolderSettingsFragment : BaseSettingsFragment() {
|
||||||
videoPath = findPreference("video_path")
|
videoPath = findPreference("video_path")
|
||||||
commandPath = findPreference("command_path")
|
commandPath = findPreference("command_path")
|
||||||
accessAllFiles = findPreference("access_all_files")
|
accessAllFiles = findPreference("access_all_files")
|
||||||
|
cacheDownloads = findPreference("cache_downloads")
|
||||||
videoFilenameTemplate = findPreference("file_name_template")
|
videoFilenameTemplate = findPreference("file_name_template")
|
||||||
audioFilenameTemplate = findPreference("file_name_template_audio")
|
audioFilenameTemplate = findPreference("file_name_template_audio")
|
||||||
clearCache = findPreference("clear_cache")
|
clearCache = findPreference("clear_cache")
|
||||||
|
|
@ -64,6 +66,10 @@ class FolderSettingsFragment : BaseSettingsFragment() {
|
||||||
if((VERSION.SDK_INT >= 30 && Environment.isExternalStorageManager()) ||
|
if((VERSION.SDK_INT >= 30 && Environment.isExternalStorageManager()) ||
|
||||||
VERSION.SDK_INT < 30) {
|
VERSION.SDK_INT < 30) {
|
||||||
accessAllFiles!!.isVisible = false
|
accessAllFiles!!.isVisible = false
|
||||||
|
cacheDownloads!!.isEnabled = true
|
||||||
|
}else{
|
||||||
|
editor.putBoolean("cache_downloads", false).apply()
|
||||||
|
cacheDownloads!!.isEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
editor.apply()
|
editor.apply()
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
package com.deniscerri.ytdlnis.ui.more.settings
|
package com.deniscerri.ytdlnis.ui.more.settings
|
||||||
|
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Build.VERSION
|
import android.os.Build.VERSION
|
||||||
import android.os.Build.VERSION_CODES
|
import android.os.Build.VERSION_CODES
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.PowerManager
|
||||||
|
import android.provider.Settings
|
||||||
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
|
||||||
|
|
@ -126,5 +130,22 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ignoreBatteryOptimization = findPreference<Preference>("ignore_battery")
|
||||||
|
val packageName: String = requireContext().packageName
|
||||||
|
val pm = requireContext().applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||||
|
if (pm.isIgnoringBatteryOptimizations(packageName)) {
|
||||||
|
ignoreBatteryOptimization!!.isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
ignoreBatteryOptimization = findPreference("ignore_battery")
|
||||||
|
ignoreBatteryOptimization!!.onPreferenceClickListener =
|
||||||
|
Preference.OnPreferenceClickListener {
|
||||||
|
val intent = Intent()
|
||||||
|
intent.action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
|
||||||
|
intent.data = Uri.parse("package:" + requireContext().packageName)
|
||||||
|
startActivity(intent)
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -15,6 +15,8 @@ import com.anggrayudi.storage.callback.FolderCallback
|
||||||
import com.anggrayudi.storage.file.copyFileTo
|
import com.anggrayudi.storage.file.copyFileTo
|
||||||
import com.anggrayudi.storage.file.copyFolderTo
|
import com.anggrayudi.storage.file.copyFolderTo
|
||||||
import com.anggrayudi.storage.file.getAbsolutePath
|
import com.anggrayudi.storage.file.getAbsolutePath
|
||||||
|
import com.anggrayudi.storage.file.moveFileTo
|
||||||
|
import com.anggrayudi.storage.file.moveTo
|
||||||
import com.deniscerri.ytdlnis.App
|
import com.deniscerri.ytdlnis.App
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.yausername.youtubedl_android.YoutubeDLRequest
|
import com.yausername.youtubedl_android.YoutubeDLRequest
|
||||||
|
|
@ -89,8 +91,47 @@ object FileUtil {
|
||||||
return@forEach
|
return@forEach
|
||||||
}
|
}
|
||||||
|
|
||||||
if(it.name.contains(".part-Frag")) return@forEach
|
runCatching {
|
||||||
|
if (File(formatPath(destDir)).canWrite()){
|
||||||
|
val files = it.listFiles()?.filter { fil -> !fil.isDirectory }?.toTypedArray() ?: arrayOf(it)
|
||||||
|
for (ff in files){
|
||||||
|
val newFile = File(dir.absolutePath + "/${ff.absolutePath.removePrefix(originDir.absolutePath)}")
|
||||||
|
runCatching {
|
||||||
|
newFile.parentFile?.mkdirs()
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= 26 ) {
|
||||||
|
var newFileName = newFile.absolutePath
|
||||||
|
var counter = 1
|
||||||
|
while (Files.exists(File(newFileName).toPath())) {
|
||||||
|
// If the file already exists in the destination directory, add a number to differentiate it
|
||||||
|
newFileName = newFile.absolutePath.replace(newFile.nameWithoutExtension, newFile.nameWithoutExtension+" ($counter)")
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
|
||||||
|
fileList.add(Files.move(
|
||||||
|
ff.toPath(),
|
||||||
|
File(newFileName).toPath(),
|
||||||
|
StandardCopyOption.REPLACE_EXISTING
|
||||||
|
).absolutePathString())
|
||||||
|
ff.delete()
|
||||||
|
fileList.add(newFileName)
|
||||||
|
}else{
|
||||||
|
var newFileName = newFile.absolutePath
|
||||||
|
var counter = 1
|
||||||
|
while (File(newFileName).exists()) {
|
||||||
|
// If the file already exists in the destination directory, add a number to differentiate it
|
||||||
|
newFileName = newFile.absolutePath.replace(newFile.nameWithoutExtension, newFile.nameWithoutExtension+" ($counter)")
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
|
||||||
|
ff.copyTo(File(newFileName),false)
|
||||||
|
ff.delete()
|
||||||
|
fileList.add(newFileName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val curr = DocumentFile.fromFile(it)
|
val curr = DocumentFile.fromFile(it)
|
||||||
val dst = DocumentFile.fromTreeUri(context, destDir.toUri())
|
val dst = DocumentFile.fromTreeUri(context, destDir.toUri())
|
||||||
|
|
@ -124,23 +165,7 @@ object FileUtil {
|
||||||
runCatching {
|
runCatching {
|
||||||
it.walkTopDown().forEach { f ->
|
it.walkTopDown().forEach { f ->
|
||||||
if (f.isDirectory) return@forEach
|
if (f.isDirectory) return@forEach
|
||||||
|
val destUri = moveFileInputStream(it, context, dst) ?: return@forEach
|
||||||
val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(f.extension) ?: "*/*"
|
|
||||||
|
|
||||||
val destUri = DocumentsContract.createDocument(
|
|
||||||
context.contentResolver,
|
|
||||||
dst.uri,
|
|
||||||
mimeType,
|
|
||||||
f.name
|
|
||||||
) ?: return@runCatching
|
|
||||||
|
|
||||||
val inputStream = f.inputStream()
|
|
||||||
val outputStream =
|
|
||||||
context.contentResolver.openOutputStream(destUri) ?: return@runCatching
|
|
||||||
inputStream.copyTo(outputStream)
|
|
||||||
inputStream.closeQuietly()
|
|
||||||
outputStream.closeQuietly()
|
|
||||||
|
|
||||||
fileList.add(DocumentFile.fromSingleUri(context, destUri)!!.getAbsolutePath(context))
|
fileList.add(DocumentFile.fromSingleUri(context, destUri)!!.getAbsolutePath(context))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -153,26 +178,11 @@ object FileUtil {
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
withContext(Dispatchers.IO){
|
withContext(Dispatchers.IO){
|
||||||
curr.copyFileTo(context, dst!!, callback = object : FileCallback() {
|
curr.moveFileTo(context, dst!!, callback = object : FileCallback() {
|
||||||
override fun onFailed(errorCode: ErrorCode) {
|
override fun onFailed(errorCode: ErrorCode) {
|
||||||
//if its usb?
|
//if its usb?
|
||||||
runCatching {
|
runCatching {
|
||||||
val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(it.extension) ?: "*/*"
|
val destUri = moveFileInputStream(it, context, dst) ?: return
|
||||||
|
|
||||||
val destUri = DocumentsContract.createDocument(
|
|
||||||
context.contentResolver,
|
|
||||||
dst.uri,
|
|
||||||
mimeType,
|
|
||||||
it.name
|
|
||||||
) ?: return@runCatching
|
|
||||||
|
|
||||||
val inputStream = it.inputStream()
|
|
||||||
val outputStream =
|
|
||||||
context.contentResolver.openOutputStream(destUri) ?: return@runCatching
|
|
||||||
inputStream.copyTo(outputStream)
|
|
||||||
inputStream.closeQuietly()
|
|
||||||
outputStream.closeQuietly()
|
|
||||||
|
|
||||||
fileList.add(DocumentFile.fromSingleUri(context, destUri)!!.getAbsolutePath(context))
|
fileList.add(DocumentFile.fromSingleUri(context, destUri)!!.getAbsolutePath(context))
|
||||||
it.delete()
|
it.delete()
|
||||||
}
|
}
|
||||||
|
|
@ -205,42 +215,6 @@ object FileUtil {
|
||||||
}
|
}
|
||||||
}catch (e: Exception) {
|
}catch (e: Exception) {
|
||||||
Log.e("error", e.message.toString())
|
Log.e("error", e.message.toString())
|
||||||
val files = it.listFiles()?.filter { fil -> !fil.isDirectory }?.toTypedArray() ?: arrayOf(it)
|
|
||||||
for (ff in files){
|
|
||||||
val newFile = File(dir.absolutePath + "/${ff.absolutePath.removePrefix(originDir.absolutePath)}")
|
|
||||||
runCatching {
|
|
||||||
newFile.parentFile?.mkdirs()
|
|
||||||
}
|
|
||||||
if (Build.VERSION.SDK_INT >= 26 ) {
|
|
||||||
var newFileName = newFile.absolutePath
|
|
||||||
var counter = 1
|
|
||||||
while (Files.exists(File(newFileName).toPath())) {
|
|
||||||
// If the file already exists in the destination directory, add a number to differentiate it
|
|
||||||
newFileName = newFile.absolutePath.replace(newFile.nameWithoutExtension, newFile.nameWithoutExtension+" ($counter)")
|
|
||||||
counter++
|
|
||||||
}
|
|
||||||
|
|
||||||
fileList.add(Files.move(
|
|
||||||
ff.toPath(),
|
|
||||||
File(newFileName).toPath(),
|
|
||||||
StandardCopyOption.REPLACE_EXISTING
|
|
||||||
).absolutePathString())
|
|
||||||
ff.delete()
|
|
||||||
fileList.add(newFileName)
|
|
||||||
}else{
|
|
||||||
var newFileName = newFile.absolutePath
|
|
||||||
var counter = 1
|
|
||||||
while (File(newFileName).exists()) {
|
|
||||||
// If the file already exists in the destination directory, add a number to differentiate it
|
|
||||||
newFileName = newFile.absolutePath.replace(newFile.nameWithoutExtension, newFile.nameWithoutExtension+" ($counter)")
|
|
||||||
counter++
|
|
||||||
}
|
|
||||||
|
|
||||||
ff.renameTo(File(newFileName))
|
|
||||||
fileList.add(newFileName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return@forEach
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -250,7 +224,28 @@ object FileUtil {
|
||||||
return@withContext scanMedia(fileList, context)
|
return@withContext scanMedia(fileList, context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private fun scanMedia(files: List<String>, context: Context) : List<String> {
|
|
||||||
|
private fun moveFileInputStream(it: File, context: Context, dst: DocumentFile) : Uri? {
|
||||||
|
val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(it.extension) ?: "*/*"
|
||||||
|
|
||||||
|
val destUri = DocumentsContract.createDocument(
|
||||||
|
context.contentResolver,
|
||||||
|
dst.uri,
|
||||||
|
mimeType,
|
||||||
|
it.name
|
||||||
|
) ?: return null
|
||||||
|
|
||||||
|
val inputStream = it.inputStream()
|
||||||
|
val outputStream =
|
||||||
|
context.contentResolver.openOutputStream(destUri) ?: return null
|
||||||
|
inputStream.copyTo(outputStream)
|
||||||
|
inputStream.closeQuietly()
|
||||||
|
outputStream.closeQuietly()
|
||||||
|
|
||||||
|
return destUri
|
||||||
|
}
|
||||||
|
|
||||||
|
fun scanMedia(files: List<String>, context: Context) : List<String> {
|
||||||
try {
|
try {
|
||||||
val paths = files.sortedByDescending { File(it).length() }
|
val paths = files.sortedByDescending { File(it).length() }
|
||||||
runCatching {MediaScannerConnection.scanFile(context, paths.toTypedArray(), null, null) }
|
runCatching {MediaScannerConnection.scanFile(context, paths.toTypedArray(), null, null) }
|
||||||
|
|
@ -259,7 +254,7 @@ object FileUtil {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
||||||
return listOf(context.getString(R.string.unfound_file))
|
return listOf()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCachePath(context: Context) : String {
|
fun getCachePath(context: Context) : String {
|
||||||
|
|
|
||||||
|
|
@ -133,13 +133,14 @@ class InfoUtil(private val context: Context) {
|
||||||
|
|
||||||
@Throws(JSONException::class)
|
@Throws(JSONException::class)
|
||||||
fun getYoutubeVideo(url: String): List<ResultItem?> {
|
fun getYoutubeVideo(url: String): List<ResultItem?> {
|
||||||
|
val theURL = url.replace("\\?list.*".toRegex(), "")
|
||||||
return try {
|
return try {
|
||||||
val id = getIDFromYoutubeURL(url)
|
val id = getIDFromYoutubeURL(theURL)
|
||||||
val res = genericRequest("$pipedURL/streams/$id")
|
val res = genericRequest("$pipedURL/streams/$id")
|
||||||
if (res.length() == 0) getFromYTDL(url) else listOf(createVideoFromPipedJSON(res, url))
|
if (res.length() == 0) getFromYTDL(theURL) else listOf(createVideoFromPipedJSON(res, theURL))
|
||||||
}catch (e: Exception){
|
}catch (e: Exception){
|
||||||
val v = getFromYTDL(url)
|
val v = getFromYTDL(theURL)
|
||||||
v.forEach { it!!.url = url }
|
v.forEach { it!!.url = theURL }
|
||||||
v
|
v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -519,8 +520,11 @@ class InfoUtil(private val context: Context) {
|
||||||
|
|
||||||
YoutubeDL.getInstance().execute(request){ progress, _, line ->
|
YoutubeDL.getInstance().execute(request){ progress, _, line ->
|
||||||
try{
|
try{
|
||||||
val listOfStrings = JSONArray(line)
|
if (line.isNotBlank()){
|
||||||
progress(parseYTDLFormats(listOfStrings))
|
val listOfStrings = JSONArray(line)
|
||||||
|
progress(parseYTDLFormats(listOfStrings))
|
||||||
|
}
|
||||||
|
|
||||||
}catch (e: Exception){
|
}catch (e: Exception){
|
||||||
progress(emptyList())
|
progress(emptyList())
|
||||||
}
|
}
|
||||||
|
|
@ -932,16 +936,38 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun buildYoutubeDLRequest(downloadItem: DownloadItem) : YoutubeDLRequest{
|
fun getFilePaths(request: YoutubeDLRequest) : List<String>{
|
||||||
val cacheDir = FileUtil.getCachePath(context)
|
return try{
|
||||||
val tempFileDir = File(cacheDir, downloadItem.id.toString())
|
request.addOption("--print", "filename")
|
||||||
tempFileDir.delete()
|
val res = YoutubeDL.getInstance().execute(request)
|
||||||
tempFileDir.mkdirs()
|
val results: Array<String?> = try {
|
||||||
|
val lineSeparator = System.getProperty("line.separator")
|
||||||
|
res.out.split(lineSeparator!!).toTypedArray()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
arrayOf(res.out)
|
||||||
|
}
|
||||||
|
results.filter { !it.isNullOrBlank() }.map { it!!.substring(0, it.lastIndexOf(".")) }.map { it.substring(it.lastIndexOf("/") + 1, it.length) }
|
||||||
|
}catch (e: Exception){
|
||||||
|
listOf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildYoutubeDLRequest(downloadItem: DownloadItem) : YoutubeDLRequest{
|
||||||
val url = downloadItem.url
|
val url = downloadItem.url
|
||||||
val request = YoutubeDLRequest(url)
|
val request = YoutubeDLRequest(url)
|
||||||
val type = downloadItem.type
|
val type = downloadItem.type
|
||||||
|
|
||||||
|
val downDir : File
|
||||||
|
if (!sharedPreferences.getBoolean("cache_downloads", true) && File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite()){
|
||||||
|
downDir = File(FileUtil.formatPath(downloadItem.downloadPath))
|
||||||
|
}else{
|
||||||
|
val cacheDir = FileUtil.getCachePath(context)
|
||||||
|
downDir = File(cacheDir, downloadItem.id.toString())
|
||||||
|
downDir.delete()
|
||||||
|
downDir.mkdirs()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
val aria2 = sharedPreferences.getBoolean("aria2", false)
|
val aria2 = sharedPreferences.getBoolean("aria2", false)
|
||||||
if (aria2) {
|
if (aria2) {
|
||||||
request.addOption("--downloader", "libaria2c.so")
|
request.addOption("--downloader", "libaria2c.so")
|
||||||
|
|
@ -1078,7 +1104,7 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
request.addOption("-P", tempFileDir.absolutePath)
|
request.addOption("-P", downDir.absolutePath)
|
||||||
|
|
||||||
if (downloadItem.audioPreferences.splitByChapters && downloadItem.downloadSections.isBlank()){
|
if (downloadItem.audioPreferences.splitByChapters && downloadItem.downloadSections.isBlank()){
|
||||||
request.addOption("--split-chapters")
|
request.addOption("--split-chapters")
|
||||||
|
|
@ -1270,7 +1296,7 @@ class InfoUtil(private val context: Context) {
|
||||||
request.addOption("--ppa", "ffmpeg:-an")
|
request.addOption("--ppa", "ffmpeg:-an")
|
||||||
}
|
}
|
||||||
|
|
||||||
request.addOption("-P", tempFileDir.absolutePath)
|
request.addOption("-P", downDir.absolutePath)
|
||||||
|
|
||||||
if (downloadItem.videoPreferences.splitByChapters && downloadItem.downloadSections.isBlank()){
|
if (downloadItem.videoPreferences.splitByChapters && downloadItem.downloadSections.isBlank()){
|
||||||
request.addOption("--split-chapters")
|
request.addOption("--split-chapters")
|
||||||
|
|
@ -1283,13 +1309,13 @@ class InfoUtil(private val context: Context) {
|
||||||
|
|
||||||
}
|
}
|
||||||
DownloadViewModel.Type.command -> {
|
DownloadViewModel.Type.command -> {
|
||||||
|
request.addOption("-P", downDir.absolutePath)
|
||||||
request.addOption(
|
request.addOption(
|
||||||
"--config-locations",
|
"--config-locations",
|
||||||
File(context.cacheDir.absolutePath + "/config[${downloadItem.id}].txt").apply {
|
File(context.cacheDir.absolutePath + "/config[${downloadItem.id}].txt").apply {
|
||||||
writeText(downloadItem.format.format_note)
|
writeText(downloadItem.format.format_note)
|
||||||
}.absolutePath
|
}.absolutePath
|
||||||
)
|
)
|
||||||
request.addOption("-P", tempFileDir.absolutePath)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
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.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
|
@ -117,7 +118,7 @@ object UiUtil {
|
||||||
fun populateCommandCard(card: MaterialCardView, item: CommandTemplate){
|
fun populateCommandCard(card: MaterialCardView, item: CommandTemplate){
|
||||||
card.findViewById<TextView>(R.id.title).text = item.title
|
card.findViewById<TextView>(R.id.title).text = item.title
|
||||||
card.findViewById<TextView>(R.id.content).text = item.content
|
card.findViewById<TextView>(R.id.content).text = item.content
|
||||||
card.findViewById<TextView>(R.id.id).text = item.id.toString()
|
card.tag = item.id
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showCommandTemplateCreationOrUpdatingSheet(item: CommandTemplate?, context: Activity, lifeCycle: LifecycleOwner, commandTemplateViewModel: CommandTemplateViewModel, newTemplate: (newTemplate: CommandTemplate) -> Unit){
|
fun showCommandTemplateCreationOrUpdatingSheet(item: CommandTemplate?, context: Activity, lifeCycle: LifecycleOwner, commandTemplateViewModel: CommandTemplateViewModel, newTemplate: (newTemplate: CommandTemplate) -> Unit){
|
||||||
|
|
@ -279,51 +280,62 @@ object UiUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun openFileIntent(fragmentContext: Context, downloadPath: String) {
|
fun openFileIntent(context: Context, downloadPath: String) {
|
||||||
val file = File(downloadPath)
|
val uri = downloadPath.runCatching {
|
||||||
val uri = FileProvider.getUriForFile(
|
DocumentFile.fromSingleUri(context, Uri.parse(downloadPath)).run{
|
||||||
fragmentContext,
|
if (this?.exists() == true){
|
||||||
fragmentContext.packageName + ".fileprovider",
|
this.uri
|
||||||
file
|
}else if (File(this@runCatching).exists()){
|
||||||
)
|
FileProvider.getUriForFile(context, context.packageName + ".fileprovider",
|
||||||
val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(file.extension)
|
File(this@runCatching))
|
||||||
val i = Intent(Intent.ACTION_VIEW)
|
}else null
|
||||||
i.setDataAndType(uri, mime)
|
}
|
||||||
i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
}.getOrNull()
|
||||||
try {
|
|
||||||
fragmentContext.startActivity(i)
|
if (uri == null){
|
||||||
}catch (e: Exception){
|
Toast.makeText(context, "Error opening file!", Toast.LENGTH_SHORT).show()
|
||||||
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
}else{
|
||||||
fragmentContext.startActivity(i)
|
Intent().apply {
|
||||||
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
action = Intent.ACTION_VIEW
|
||||||
|
data = uri
|
||||||
|
}.run{
|
||||||
|
context.startActivity(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun shareFileIntent(fragmentContext: Context, paths: List<String>){
|
fun shareFileIntent(context: Context, paths: List<String>){
|
||||||
val uris : ArrayList<Uri> = arrayListOf()
|
val uris : ArrayList<Uri> = arrayListOf()
|
||||||
paths.forEach {
|
paths.runCatching {
|
||||||
val file = File(it)
|
this.forEach {path ->
|
||||||
if (! file.exists()) return@forEach
|
val uri = DocumentFile.fromSingleUri(context, Uri.parse(path)).run{
|
||||||
val uri = FileProvider.getUriForFile(
|
if (this?.exists() == true){
|
||||||
fragmentContext,
|
this.uri
|
||||||
fragmentContext.packageName + ".fileprovider",
|
}else if (File(path).exists()){
|
||||||
file
|
FileProvider.getUriForFile(context, context.packageName + ".fileprovider",
|
||||||
)
|
File(path))
|
||||||
uris.add(uri)
|
}else null
|
||||||
|
}
|
||||||
|
if (uri != null) uris.add(uri)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (uris.isEmpty()){
|
||||||
val shareIntent: Intent = Intent().apply {
|
Toast.makeText(context, "Error sharing files!", Toast.LENGTH_SHORT).show()
|
||||||
action = Intent.ACTION_SEND_MULTIPLE
|
}else{
|
||||||
putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris)
|
Intent().apply {
|
||||||
type = "*/*"
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
}
|
action = Intent.ACTION_SEND_MULTIPLE
|
||||||
try {
|
putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris)
|
||||||
fragmentContext.startActivity(Intent.createChooser(shareIntent, fragmentContext.getString(R.string.share)))
|
type = if (uris.size == 1) uris[0].let { context.contentResolver.getType(it) } ?: "media/*" else "*/*"
|
||||||
}catch (e: Exception){
|
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
|
||||||
val intent = Intent.createChooser(shareIntent, fragmentContext.getString(R.string.share))
|
}.run{
|
||||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
context.startActivity(Intent.createChooser(this, context.getString(R.string.share)))
|
||||||
fragmentContext.startActivity(intent)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -361,6 +373,24 @@ object UiUtil {
|
||||||
datePicker.show(fragmentManager, "datepicker")
|
datePicker.show(fragmentManager, "datepicker")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun showTimePicker(fragmentManager: FragmentManager , onSubmit : (chosenTime: Calendar) -> Unit ){
|
||||||
|
val currentDate = Calendar.getInstance()
|
||||||
|
val date = Calendar.getInstance()
|
||||||
|
|
||||||
|
val timepicker = MaterialTimePicker.Builder()
|
||||||
|
.setTimeFormat(TimeFormat.CLOCK_24H)
|
||||||
|
.setHour(currentDate.get(Calendar.HOUR_OF_DAY))
|
||||||
|
.setMinute(currentDate.get(Calendar.MINUTE))
|
||||||
|
.build()
|
||||||
|
|
||||||
|
timepicker.addOnPositiveButtonClickListener{
|
||||||
|
date[Calendar.HOUR_OF_DAY] = timepicker.hour
|
||||||
|
date[Calendar.MINUTE] = timepicker.minute
|
||||||
|
onSubmit(date)
|
||||||
|
}
|
||||||
|
timepicker.show(fragmentManager, "timepicker")
|
||||||
|
}
|
||||||
|
|
||||||
fun showDownloadItemDetailsCard(
|
fun showDownloadItemDetailsCard(
|
||||||
item: DownloadItem,
|
item: DownloadItem,
|
||||||
context: Activity,
|
context: Activity,
|
||||||
|
|
|
||||||
116
app/src/main/java/com/deniscerri/ytdlnis/work/AlarmScheduler.kt
Normal file
116
app/src/main/java/com/deniscerri/ytdlnis/work/AlarmScheduler.kt
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
package com.deniscerri.ytdlnis.work
|
||||||
|
|
||||||
|
import android.app.AlarmManager
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import com.deniscerri.ytdlnis.receiver.AlarmStartReceiver
|
||||||
|
import com.deniscerri.ytdlnis.receiver.AlarmStopReceiver
|
||||||
|
import java.util.Calendar
|
||||||
|
|
||||||
|
class AlarmScheduler(private val context: Context) {
|
||||||
|
|
||||||
|
private val alarmManager = context.getSystemService(AlarmManager::class.java)
|
||||||
|
private val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
|
||||||
|
fun schedule() {
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
//schedule starting alarm
|
||||||
|
val startingTime = preferences.getString("schedule_start", "00:00")!!
|
||||||
|
val sTime = Calendar.getInstance()
|
||||||
|
sTime.set(Calendar.HOUR_OF_DAY, startingTime.split(":")[0].toInt())
|
||||||
|
sTime.set(Calendar.MINUTE, startingTime.split(":")[1].toInt())
|
||||||
|
sTime.set(Calendar.SECOND, 0)
|
||||||
|
val time = calculateNextTime(sTime)
|
||||||
|
|
||||||
|
val intent = Intent(context, AlarmStartReceiver::class.java)
|
||||||
|
alarmManager.setExactAndAllowWhileIdle(
|
||||||
|
AlarmManager.RTC,
|
||||||
|
time.timeInMillis,
|
||||||
|
PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE )
|
||||||
|
)
|
||||||
|
|
||||||
|
//schedule closing alarm
|
||||||
|
val endingTime = preferences.getString("schedule_end", "05:00")!!
|
||||||
|
val eTime = Calendar.getInstance()
|
||||||
|
eTime.set(Calendar.HOUR_OF_DAY, endingTime.split(":")[0].toInt())
|
||||||
|
eTime.set(Calendar.MINUTE, endingTime.split(":")[1].toInt())
|
||||||
|
sTime.set(Calendar.SECOND, 0)
|
||||||
|
val calendar = calculateNextTime(eTime)
|
||||||
|
|
||||||
|
|
||||||
|
val intent2 = Intent(context, AlarmStopReceiver::class.java)
|
||||||
|
alarmManager.setExactAndAllowWhileIdle(
|
||||||
|
AlarmManager.RTC,
|
||||||
|
calendar.timeInMillis + 60000,
|
||||||
|
PendingIntent.getBroadcast(context, 1, intent2, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE )
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancel() {
|
||||||
|
alarmManager.cancel(
|
||||||
|
PendingIntent.getBroadcast(
|
||||||
|
context,
|
||||||
|
0,
|
||||||
|
Intent(context, AlarmStartReceiver::class.java),
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
alarmManager.cancel(
|
||||||
|
PendingIntent.getBroadcast(
|
||||||
|
context,
|
||||||
|
1,
|
||||||
|
Intent(context, AlarmStopReceiver::class.java),
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun calculateNextTime(c: Calendar) : Calendar {
|
||||||
|
val calendar = Calendar.getInstance()
|
||||||
|
if (c.get(Calendar.HOUR_OF_DAY) < calendar.get(Calendar.HOUR_OF_DAY)){
|
||||||
|
c.add(Calendar.DATE, 1)
|
||||||
|
}else if (
|
||||||
|
c.get(Calendar.HOUR_OF_DAY) == calendar.get(Calendar.HOUR_OF_DAY) &&
|
||||||
|
c.get(Calendar.MINUTE) < calendar.get(Calendar.MINUTE)
|
||||||
|
){
|
||||||
|
c.add(Calendar.DATE, 1)
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isDuringTheScheduledTime(): Boolean{
|
||||||
|
val now = Calendar.getInstance()
|
||||||
|
val currentHour = now.get(Calendar.HOUR_OF_DAY)
|
||||||
|
val currentMinute = now.get(Calendar.MINUTE)
|
||||||
|
|
||||||
|
val startingTime = preferences.getString("schedule_start", "00:00")!!
|
||||||
|
val startingHour = startingTime.split(":")[0].toInt()
|
||||||
|
val startingMinute = startingTime.split(":")[1].toInt()
|
||||||
|
|
||||||
|
val endingTime = preferences.getString("schedule_end", "05:00")!!
|
||||||
|
var endingHour = endingTime.split(":")[0].toInt()
|
||||||
|
if (endingHour < 12 && endingHour < startingHour){
|
||||||
|
endingHour += 24
|
||||||
|
}
|
||||||
|
val endingMinute = endingTime.split(":")[1].toInt()
|
||||||
|
|
||||||
|
if (currentHour in startingHour..endingHour){
|
||||||
|
if (currentHour == endingHour){
|
||||||
|
if (currentMinute > endingMinute) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}else if(currentHour == startingHour){
|
||||||
|
if (currentMinute < startingMinute){
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -35,6 +35,7 @@ import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.util.Calendar
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -53,6 +54,7 @@ class DownloadWorker(
|
||||||
val resultDao = dbManager.resultDao
|
val resultDao = dbManager.resultDao
|
||||||
val logRepo = LogRepository(dbManager.logDao)
|
val logRepo = LogRepository(dbManager.logDao)
|
||||||
val handler = Handler(Looper.getMainLooper())
|
val handler = Handler(Looper.getMainLooper())
|
||||||
|
val alarmScheduler = AlarmScheduler(context)
|
||||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
val time = System.currentTimeMillis() + 6000
|
val time = System.currentTimeMillis() + 6000
|
||||||
val queuedItems = dao.getQueuedDownloadsThatAreNotScheduledChunked(time)
|
val queuedItems = dao.getQueuedDownloadsThatAreNotScheduledChunked(time)
|
||||||
|
|
@ -75,8 +77,14 @@ class DownloadWorker(
|
||||||
}
|
}
|
||||||
|
|
||||||
val running = ArrayList(runningYTDLInstances)
|
val running = ArrayList(runningYTDLInstances)
|
||||||
|
val useScheduler = sharedPreferences.getBoolean("use_scheduler", false)
|
||||||
if (items.isEmpty() && running.isEmpty()) WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id)
|
if (items.isEmpty() && running.isEmpty()) WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id)
|
||||||
|
|
||||||
|
if (useScheduler){
|
||||||
|
if (items.none{it.downloadStartTime > 0L} && running.isEmpty() && !alarmScheduler.isDuringTheScheduledTime()) {
|
||||||
|
WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
val concurrentDownloads = sharedPreferences.getInt("concurrent_downloads", 1) - running.size
|
val concurrentDownloads = sharedPreferences.getInt("concurrent_downloads", 1) - running.size
|
||||||
val eligibleDownloads = items.take(if (concurrentDownloads < 0) 0 else concurrentDownloads).filter { it.id !in running }
|
val eligibleDownloads = items.take(if (concurrentDownloads < 0) 0 else concurrentDownloads).filter { it.id !in running }
|
||||||
|
|
||||||
|
|
@ -114,7 +122,7 @@ class DownloadWorker(
|
||||||
"Format: ${downloadItem.format}\n\n"
|
"Format: ${downloadItem.format}\n\n"
|
||||||
}
|
}
|
||||||
else { "" } +
|
else { "" } +
|
||||||
"Command: ${infoUtil.parseYTDLRequestString(request)} ${downloadItem.extraCommands}\n\n",
|
"Command:\n ${infoUtil.parseYTDLRequestString(request)} ${downloadItem.extraCommands}\n\n",
|
||||||
downloadItem.format,
|
downloadItem.format,
|
||||||
downloadItem.type,
|
downloadItem.type,
|
||||||
System.currentTimeMillis(),
|
System.currentTimeMillis(),
|
||||||
|
|
@ -147,33 +155,47 @@ class DownloadWorker(
|
||||||
}
|
}
|
||||||
}.onSuccess {
|
}.onSuccess {
|
||||||
runningYTDLInstances.remove(downloadItem.id)
|
runningYTDLInstances.remove(downloadItem.id)
|
||||||
FileUtil.deleteConfigFiles(request)
|
|
||||||
|
|
||||||
val wasQuickDownloaded = updateDownloadItem(downloadItem, infoUtil, dao, resultDao)
|
val wasQuickDownloaded = updateDownloadItem(downloadItem, infoUtil, dao, resultDao)
|
||||||
runBlocking {
|
runBlocking {
|
||||||
var finalPaths : List<String>?
|
var finalPaths : List<String>?
|
||||||
//move file from internal to set download directory
|
|
||||||
setProgressAsync(workDataOf("progress" to 100, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id))
|
|
||||||
try {
|
|
||||||
finalPaths = withContext(Dispatchers.IO){
|
|
||||||
FileUtil.moveFile(tempFileDir.absoluteFile,context, downloadLocation, keepCache){ p ->
|
|
||||||
setProgressAsync(workDataOf("progress" to p, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (finalPaths.isNotEmpty()){
|
//if there was no cache used
|
||||||
setProgressAsync(workDataOf("progress" to 100, "output" to "Moved file to $downloadLocation", "id" to downloadItem.id))
|
if (!sharedPreferences.getBoolean("cache_downloads", true) && File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite()){
|
||||||
}else{
|
setProgressAsync(workDataOf("progress" to 100, "output" to "Scanning Files", "id" to downloadItem.id))
|
||||||
|
val p = infoUtil.getFilePaths(request)
|
||||||
|
finalPaths = File(FileUtil.formatPath(downloadLocation))
|
||||||
|
.walkTopDown()
|
||||||
|
.filter { it.isFile && p.contains(it.nameWithoutExtension) }
|
||||||
|
.sortedByDescending { it.length() }
|
||||||
|
.map { it.absolutePath }
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
FileUtil.scanMedia(finalPaths, context)
|
||||||
|
}else{
|
||||||
|
//move file from internal to set download directory
|
||||||
|
setProgressAsync(workDataOf("progress" to 100, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id))
|
||||||
|
try {
|
||||||
|
finalPaths = withContext(Dispatchers.IO){
|
||||||
|
FileUtil.moveFile(tempFileDir.absoluteFile,context, downloadLocation, keepCache){ p ->
|
||||||
|
setProgressAsync(workDataOf("progress" to p, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (finalPaths.isNotEmpty()){
|
||||||
|
setProgressAsync(workDataOf("progress" to 100, "output" to "Moved file to $downloadLocation", "id" to downloadItem.id))
|
||||||
|
}else{
|
||||||
|
finalPaths = listOf(context.getString(R.string.unfound_file))
|
||||||
|
}
|
||||||
|
}catch (e: Exception){
|
||||||
finalPaths = listOf(context.getString(R.string.unfound_file))
|
finalPaths = listOf(context.getString(R.string.unfound_file))
|
||||||
|
e.printStackTrace()
|
||||||
|
handler.postDelayed({
|
||||||
|
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
|
||||||
|
}, 1000)
|
||||||
}
|
}
|
||||||
}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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FileUtil.deleteConfigFiles(request)
|
||||||
|
|
||||||
//put download in history
|
//put download in history
|
||||||
val incognito = sharedPreferences.getBoolean("incognito", false)
|
val incognito = sharedPreferences.getBoolean("incognito", false)
|
||||||
|
|
|
||||||
5
app/src/main/res/drawable/baseline_save_as_24.xml
Normal file
5
app/src/main/res/drawable/baseline_save_as_24.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<vector android:height="24dp"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="?android:colorAccent" android:pathData="M21,12.4V7l-4,-4H5C3.89,3 3,3.9 3,5v14c0,1.1 0.89,2 2,2h7.4L21,12.4zM15,15c0,1.66 -1.34,3 -3,3s-3,-1.34 -3,-3s1.34,-3 3,-3S15,13.34 15,15zM6,6h9v4H6V6zM19.99,16.25l1.77,1.77L16.77,23H15v-1.77L19.99,16.25zM23.25,16.51l-0.85,0.85l-1.77,-1.77l0.85,-0.85c0.2,-0.2 0.51,-0.2 0.71,0l1.06,1.06C23.45,16 23.45,16.32 23.25,16.51z"/>
|
||||||
|
</vector>
|
||||||
|
|
@ -19,13 +19,6 @@
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:padding="15dp">
|
android:padding="15dp">
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:visibility="gone"
|
|
||||||
android:id="@+id/id"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
tools:ignore="MissingConstraints" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/title"
|
android:id="@+id/title"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
|
|
||||||
|
|
@ -57,9 +57,9 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="10dp"
|
android:layout_marginEnd="10dp"
|
||||||
app:icon="@drawable/ic_clock"
|
app:icon="@drawable/ic_clock"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="@id/bottomsheet_download_button"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/bottomsheet_download_button"
|
app:layout_constraintEnd_toStartOf="@+id/bottomsheet_download_button"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="@id/bottomsheet_download_button" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/bottomsheet_download_button"
|
android:id="@+id/bottomsheet_download_button"
|
||||||
|
|
|
||||||
|
|
@ -37,11 +37,9 @@
|
||||||
android:paddingBottom="150dp"
|
android:paddingBottom="150dp"
|
||||||
>
|
>
|
||||||
|
|
||||||
<LinearLayout
|
<HorizontalScrollView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
android:focusableInTouchMode="true"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/content"
|
android:id="@+id/content"
|
||||||
|
|
@ -49,11 +47,12 @@
|
||||||
android:fontFamily="monospace"
|
android:fontFamily="monospace"
|
||||||
android:textSize="15sp"
|
android:textSize="15sp"
|
||||||
android:gravity="bottom"
|
android:gravity="bottom"
|
||||||
android:layout_width="match_parent"
|
android:scrollbars="vertical|horizontal"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
android:textIsSelectable="true"
|
android:textIsSelectable="true"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
</LinearLayout>
|
</HorizontalScrollView>
|
||||||
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,24 @@
|
||||||
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
<navigation 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"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:label="@string/more"
|
android:id="@+id/main_nav"
|
||||||
app:startDestination="@id/homeFragment">
|
app:startDestination="@id/homeFragment">
|
||||||
|
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_mainSettingsFragment_to_folderSettingsFragment"
|
||||||
|
app:destination="@id/settingsNav" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_mainSettingsFragment_to_updateSettingsFragment"
|
||||||
|
app:destination="@id/settingsNav" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_mainSettingsFragment_to_processingSettingsFragment"
|
||||||
|
app:destination="@id/settingsNav" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_mainSettingsFragment_to_appearanceSettingsFragment"
|
||||||
|
app:destination="@id/settingsNav" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_mainSettingsFragment_to_downloadSettingsFragment"
|
||||||
|
app:destination="@id/settingsNav" />
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/homeFragment"
|
android:id="@+id/homeFragment"
|
||||||
android:name="com.deniscerri.ytdlnis.ui.HomeFragment"
|
android:name="com.deniscerri.ytdlnis.ui.HomeFragment"
|
||||||
|
|
@ -46,17 +61,15 @@
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_moreFragment_to_downloadLogListFragment2"
|
android:id="@+id/action_moreFragment_to_downloadLogListFragment2"
|
||||||
app:destination="@id/downloadLogListFragment" />
|
app:destination="@id/downloadLogListFragment" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_moreFragment_to_settingsNav"
|
||||||
|
app:destination="@id/settingsNav" />
|
||||||
</fragment>
|
</fragment>
|
||||||
<activity
|
<activity
|
||||||
android:id="@+id/terminalActivity"
|
android:id="@+id/terminalActivity"
|
||||||
android:name="com.deniscerri.ytdlnis.ui.more.TerminalActivity"
|
android:name="com.deniscerri.ytdlnis.ui.more.TerminalActivity"
|
||||||
android:label="activity_terminal"
|
android:label="activity_terminal"
|
||||||
tools:layout="@layout/activity_terminal" />
|
tools:layout="@layout/activity_terminal" />
|
||||||
<activity
|
|
||||||
android:id="@+id/activitySettings"
|
|
||||||
android:name="com.deniscerri.ytdlnis.ui.more.settings.SettingsActivity"
|
|
||||||
android:label="activity_settings"
|
|
||||||
tools:layout="@layout/activity_settings" />
|
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/cookiesFragment"
|
android:id="@+id/cookiesFragment"
|
||||||
android:name="com.deniscerri.ytdlnis.ui.more.CookiesFragment"
|
android:name="com.deniscerri.ytdlnis.ui.more.CookiesFragment"
|
||||||
|
|
@ -88,4 +101,52 @@
|
||||||
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"
|
||||||
|
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
|
||||||
|
android:id="@+id/folderSettingsFragment"
|
||||||
|
android:name="com.deniscerri.ytdlnis.ui.more.settings.FolderSettingsFragment"
|
||||||
|
android:label="FolderSettingsFragment" />
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/processingSettingsFragment"
|
||||||
|
android:name="com.deniscerri.ytdlnis.ui.more.settings.ProcessingSettingsFragment"
|
||||||
|
android:label="ProcessingSettingsFragment" />
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/downloadSettingsFragment"
|
||||||
|
android:name="com.deniscerri.ytdlnis.ui.more.settings.DownloadSettingsFragment"
|
||||||
|
android:label="DownloadSettingsFragment" />
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/updateSettingsFragment"
|
||||||
|
android:name="com.deniscerri.ytdlnis.ui.more.settings.UpdateSettingsFragment"
|
||||||
|
android:label="UpdateSettingsFragment" />
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/mainSettingsFragment"
|
||||||
|
android:name="com.deniscerri.ytdlnis.ui.more.settings.MainSettingsFragment"
|
||||||
|
android:label="MainSettingsFragment">
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_mainSettingsFragment_to_appearanceSettingsFragment"
|
||||||
|
app:destination="@id/appearanceSettingsFragment" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_mainSettingsFragment_to_folderSettingsFragment"
|
||||||
|
app:destination="@id/folderSettingsFragment" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_mainSettingsFragment_to_downloadSettingsFragment"
|
||||||
|
app:destination="@id/downloadSettingsFragment" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_mainSettingsFragment_to_processingSettingsFragment"
|
||||||
|
app:destination="@id/processingSettingsFragment" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_mainSettingsFragment_to_updateSettingsFragment"
|
||||||
|
app:destination="@id/updateSettingsFragment" />
|
||||||
|
</fragment>
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/appearanceSettingsFragment"
|
||||||
|
android:name="com.deniscerri.ytdlnis.ui.more.settings.GeneralSettingsFragment"
|
||||||
|
android:label="AppearanceSettingsFragment" />
|
||||||
|
</navigation>
|
||||||
</navigation>
|
</navigation>
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name" translatable="false">YTDLnis</string>
|
|
||||||
<string name="search">Arama</string>
|
<string name="search">Arama</string>
|
||||||
<string name="remove_all">Hepsini Kaldır</string>
|
<string name="remove_all">Hepsini Kaldır</string>
|
||||||
<string name="remove_results">Sonuçları Temizle</string>
|
<string name="remove_results">Sonuçları Temizle</string>
|
||||||
|
|
|
||||||
|
|
@ -735,6 +735,7 @@
|
||||||
<item>@string/in_queue</item>
|
<item>@string/in_queue</item>
|
||||||
<item>@string/cancelled</item>
|
<item>@string/cancelled</item>
|
||||||
<item>@string/errored</item>
|
<item>@string/errored</item>
|
||||||
|
<item>@string/saved</item>
|
||||||
<item>@string/cookies</item>
|
<item>@string/cookies</item>
|
||||||
<item>@string/command_templates</item>
|
<item>@string/command_templates</item>
|
||||||
<item>@string/shortcuts</item>
|
<item>@string/shortcuts</item>
|
||||||
|
|
@ -747,6 +748,7 @@
|
||||||
<item>queued</item>
|
<item>queued</item>
|
||||||
<item>cancelled</item>
|
<item>cancelled</item>
|
||||||
<item>errored</item>
|
<item>errored</item>
|
||||||
|
<item>saved</item>
|
||||||
<item>cookies</item>
|
<item>cookies</item>
|
||||||
<item>templates</item>
|
<item>templates</item>
|
||||||
<item>shortcuts</item>
|
<item>shortcuts</item>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
<resources>
|
<resources>
|
||||||
<string name="worst_quality">Worst Quality</string>
|
<string name="worst_quality">Worst Quality</string>
|
||||||
<string name="best_quality">Best Quality</string>
|
<string name="best_quality">Best Quality</string>
|
||||||
<string name="app_name" translatable="false">YTDLnis</string>
|
|
||||||
<string name="search">Search</string>
|
<string name="search">Search</string>
|
||||||
<string name="remove_all">Remove All</string>
|
<string name="remove_all">Remove All</string>
|
||||||
<string name="remove_results">Clear Results</string>
|
<string name="remove_results">Clear Results</string>
|
||||||
|
|
@ -304,4 +303,8 @@
|
||||||
<string name="update_formats_background">Update Formats in Background</string>
|
<string name="update_formats_background">Update Formats in Background</string>
|
||||||
<string name="auto_update_ytdlp">Auto-Update yt-dlp</string>
|
<string name="auto_update_ytdlp">Auto-Update yt-dlp</string>
|
||||||
<string name="use_sponsorblock">Use SponsorBlock</string>
|
<string name="use_sponsorblock">Use SponsorBlock</string>
|
||||||
|
<string name="cache_first">Cache Downloads First</string>
|
||||||
|
<string name="cache_first_summary">Download in the internal storage then transfer afterwards. Use this when you can\'t write directly to storage</string>
|
||||||
|
<string name="scheduling">Scheduling</string>
|
||||||
|
<string name="use_scheduler">Download On Schedule</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -3,111 +3,134 @@
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<PreferenceCategory app:title="@string/downloading">
|
||||||
android:widgetLayout="@layout/preferece_material_switch"
|
<SwitchPreferenceCompat
|
||||||
app:defaultValue="false"
|
android:widgetLayout="@layout/preferece_material_switch"
|
||||||
android:icon="@drawable/ic_incognito"
|
app:defaultValue="false"
|
||||||
android:key="incognito"
|
android:icon="@drawable/ic_incognito"
|
||||||
app:summary="@string/incognito_summary"
|
android:key="incognito"
|
||||||
app:title="@string/incognito" />
|
app:summary="@string/incognito_summary"
|
||||||
|
app:title="@string/incognito" />
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
android:widgetLayout="@layout/preferece_material_switch"
|
android:widgetLayout="@layout/preferece_material_switch"
|
||||||
app:defaultValue="true"
|
app:defaultValue="true"
|
||||||
android:icon="@drawable/ic_card"
|
android:icon="@drawable/ic_card"
|
||||||
android:key="download_card"
|
android:key="download_card"
|
||||||
app:summary="@string/download_card_summary"
|
app:summary="@string/download_card_summary"
|
||||||
app:title="@string/show_download_card" />
|
app:title="@string/show_download_card" />
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
android:widgetLayout="@layout/preferece_material_switch"
|
android:widgetLayout="@layout/preferece_material_switch"
|
||||||
app:defaultValue="false"
|
app:defaultValue="false"
|
||||||
android:icon="@drawable/ic_speed"
|
android:icon="@drawable/ic_speed"
|
||||||
android:key="quick_download"
|
android:key="quick_download"
|
||||||
app:summary="@string/quick_download_summary"
|
app:summary="@string/quick_download_summary"
|
||||||
app:title="@string/quick_download" />
|
app:title="@string/quick_download" />
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
android:widgetLayout="@layout/preferece_material_switch"
|
android:widgetLayout="@layout/preferece_material_switch"
|
||||||
app:defaultValue="true"
|
app:defaultValue="true"
|
||||||
android:icon="@drawable/metered_networks"
|
android:icon="@drawable/metered_networks"
|
||||||
android:key="metered_networks"
|
android:key="metered_networks"
|
||||||
app:summary="@string/download_over_metered_networks_summary"
|
app:summary="@string/download_over_metered_networks_summary"
|
||||||
app:title="@string/download_over_metered_networks" />
|
app:title="@string/download_over_metered_networks" />
|
||||||
|
|
||||||
|
|
||||||
<EditTextPreference
|
<EditTextPreference
|
||||||
android:icon="@drawable/baseline_network_locked_24"
|
android:icon="@drawable/baseline_network_locked_24"
|
||||||
app:key="proxy"
|
app:key="proxy"
|
||||||
app:defaultValue=""
|
app:defaultValue=""
|
||||||
app:summary="@string/socks5_proxy_summary"
|
app:summary="@string/socks5_proxy_summary"
|
||||||
app:title="@string/socks5_proxy" />
|
app:title="@string/socks5_proxy" />
|
||||||
|
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:defaultValue="video"
|
android:defaultValue="video"
|
||||||
android:entries="@array/download_types"
|
android:entries="@array/download_types"
|
||||||
android:entryValues="@array/download_types_values"
|
android:entryValues="@array/download_types_values"
|
||||||
android:icon="@drawable/ic_download_type"
|
android:icon="@drawable/ic_download_type"
|
||||||
app:key="preferred_download_type"
|
app:key="preferred_download_type"
|
||||||
app:summary="@string/preferred_download_type_summary"
|
app:summary="@string/preferred_download_type_summary"
|
||||||
app:title="@string/preferred_download_type" />
|
app:title="@string/preferred_download_type" />
|
||||||
|
|
||||||
<SeekBarPreference
|
<SeekBarPreference
|
||||||
app:dependency="aria2"
|
app:dependency="aria2"
|
||||||
android:defaultValue="3"
|
android:defaultValue="3"
|
||||||
android:icon="@drawable/ic_lines"
|
android:icon="@drawable/ic_lines"
|
||||||
android:max="25"
|
android:max="25"
|
||||||
app:key="concurrent_fragments"
|
app:key="concurrent_fragments"
|
||||||
app:min="1"
|
app:min="1"
|
||||||
app:showSeekBarValue="true"
|
app:showSeekBarValue="true"
|
||||||
android:dependency="aria2"
|
android:dependency="aria2"
|
||||||
app:summary="@string/concurrent_fragments_summary"
|
app:summary="@string/concurrent_fragments_summary"
|
||||||
app:title="@string/concurrent_fragments" />
|
app:title="@string/concurrent_fragments" />
|
||||||
|
|
||||||
<SeekBarPreference
|
<SeekBarPreference
|
||||||
android:defaultValue="1"
|
android:defaultValue="1"
|
||||||
android:icon="@drawable/ic_concurrent_downloads"
|
android:icon="@drawable/ic_concurrent_downloads"
|
||||||
android:max="10"
|
android:max="10"
|
||||||
app:key="concurrent_downloads"
|
app:key="concurrent_downloads"
|
||||||
app:min="1"
|
app:min="1"
|
||||||
app:showSeekBarValue="true"
|
app:showSeekBarValue="true"
|
||||||
app:summary="@string/concurrent_downloads_summary"
|
app:summary="@string/concurrent_downloads_summary"
|
||||||
app:title="@string/concurrent_downloads" />
|
app:title="@string/concurrent_downloads" />
|
||||||
|
|
||||||
<EditTextPreference
|
<EditTextPreference
|
||||||
android:icon="@drawable/ic_down"
|
android:icon="@drawable/ic_down"
|
||||||
app:key="limit_rate"
|
app:key="limit_rate"
|
||||||
app:summary="@string/limit_rate_summary"
|
app:summary="@string/limit_rate_summary"
|
||||||
app:title="@string/limit_rate" />
|
app:title="@string/limit_rate" />
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
android:widgetLayout="@layout/preferece_material_switch"
|
android:widgetLayout="@layout/preferece_material_switch"
|
||||||
app:defaultValue="false"
|
app:defaultValue="false"
|
||||||
android:icon="@drawable/ic_speed"
|
android:icon="@drawable/ic_speed"
|
||||||
android:key="aria2"
|
android:key="aria2"
|
||||||
app:summary="@string/aria2_summary"
|
app:summary="@string/aria2_summary"
|
||||||
android:disableDependentsState="true"
|
android:disableDependentsState="true"
|
||||||
app:title="Aria2" />
|
app:title="Aria2" />
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
android:widgetLayout="@layout/preferece_material_switch"
|
android:widgetLayout="@layout/preferece_material_switch"
|
||||||
app:defaultValue="false"
|
app:defaultValue="false"
|
||||||
android:icon="@drawable/ic_baseline_file_open_24"
|
android:icon="@drawable/ic_baseline_file_open_24"
|
||||||
android:key="log_downloads"
|
android:key="log_downloads"
|
||||||
app:summary="@string/log_downloads_summary"
|
app:summary="@string/log_downloads_summary"
|
||||||
app:title="@string/log_downloads" />
|
app:title="@string/log_downloads" />
|
||||||
|
|
||||||
<EditTextPreference
|
<EditTextPreference
|
||||||
app:key="retries"
|
app:key="retries"
|
||||||
app:title="@string/retries" />
|
app:title="@string/retries" />
|
||||||
|
|
||||||
<EditTextPreference
|
<EditTextPreference
|
||||||
app:key="fragment_retries"
|
app:key="fragment_retries"
|
||||||
app:title="@string/fragment_retries" />
|
app:title="@string/fragment_retries" />
|
||||||
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
<PreferenceCategory app:title="@string/scheduling">
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:widgetLayout="@layout/preferece_material_switch"
|
||||||
|
app:defaultValue="false"
|
||||||
|
android:icon="@drawable/ic_baseline_file_open_24"
|
||||||
|
android:key="use_scheduler"
|
||||||
|
app:title="@string/use_scheduler" />
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:dependency="use_scheduler"
|
||||||
|
app:title="@string/start"
|
||||||
|
app:defaultValue="00:00"
|
||||||
|
app:summary="12:00"
|
||||||
|
android:key="schedule_start"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:dependency="use_scheduler"
|
||||||
|
app:title="@string/end"
|
||||||
|
app:defaultValue="05:00"
|
||||||
|
app:summary="05:00"
|
||||||
|
android:key="schedule_end"
|
||||||
|
/>
|
||||||
|
</PreferenceCategory>
|
||||||
|
|
||||||
<Preference
|
|
||||||
app:icon="@drawable/ic_battery"
|
|
||||||
app:key="ignore_battery"
|
|
||||||
app:title="@string/ignore_battery_optimization" />
|
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|
@ -23,6 +23,25 @@
|
||||||
android:summary="@string/access_all_directories_summary"
|
android:summary="@string/access_all_directories_summary"
|
||||||
app:title="@string/access_all_directories" />
|
app:title="@string/access_all_directories" />
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:widgetLayout="@layout/preferece_material_switch"
|
||||||
|
app:defaultValue="true"
|
||||||
|
app:icon="@drawable/if_file_rename"
|
||||||
|
android:enabled="false"
|
||||||
|
app:key="cache_downloads"
|
||||||
|
app:summary="@string/cache_first_summary"
|
||||||
|
app:title="@string/cache_first" />
|
||||||
|
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:widgetLayout="@layout/preferece_material_switch"
|
||||||
|
app:defaultValue="false"
|
||||||
|
android:icon="@drawable/baseline_save_24"
|
||||||
|
android:key="keep_cache"
|
||||||
|
app:summary="@string/keep_cache_summary"
|
||||||
|
app:title="@string/keep_cache" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<EditTextPreference
|
<EditTextPreference
|
||||||
android:icon="@drawable/ic_textformat"
|
android:icon="@drawable/ic_textformat"
|
||||||
|
|
@ -58,12 +77,4 @@
|
||||||
android:summary="@string/clear_temporary_files_summary"
|
android:summary="@string/clear_temporary_files_summary"
|
||||||
app:title="@string/clear_temporary_files" />
|
app:title="@string/clear_temporary_files" />
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
|
||||||
android:widgetLayout="@layout/preferece_material_switch"
|
|
||||||
app:defaultValue="false"
|
|
||||||
android:icon="@drawable/baseline_save_24"
|
|
||||||
android:key="keep_cache"
|
|
||||||
app:summary="@string/keep_cache_summary"
|
|
||||||
app:title="@string/keep_cache" />
|
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|
@ -116,5 +116,11 @@
|
||||||
android:icon="@drawable/baseline_share_24"
|
android:icon="@drawable/baseline_share_24"
|
||||||
android:key="show_terminal"
|
android:key="show_terminal"
|
||||||
app:title="@string/show_terminal" />
|
app:title="@string/show_terminal" />
|
||||||
|
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
app:icon="@drawable/ic_battery"
|
||||||
|
app:key="ignore_battery"
|
||||||
|
app:title="@string/ignore_battery_optimization" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
android:widgetLayout="@layout/preferece_material_switch"
|
android:widgetLayout="@layout/preferece_material_switch"
|
||||||
android:defaultValue="false"
|
android:defaultValue="true"
|
||||||
app:icon="@drawable/ic_nightly"
|
app:icon="@drawable/ic_nightly"
|
||||||
app:key="nightly_ytdl"
|
app:key="nightly_ytdl"
|
||||||
android:title="@string/update_ytdl_nightly"
|
android:title="@string/update_ytdl_nightly"
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,9 @@ buildscript {
|
||||||
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'
|
||||||
|
|
||||||
|
RELEASE_NAME = 'YTDLnis'
|
||||||
|
BETA_NAME = 'YTDLnis [BETA]'
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue