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 versionMinor = 6
|
||||
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 = ""
|
||||
|
||||
if (versionBuild > 0){
|
||||
|
|
@ -63,6 +63,7 @@ android {
|
|||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
manifestPlaceholders = [appName: RELEASE_NAME]
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
debuggable false
|
||||
signingConfig signingConfigs.debug
|
||||
|
|
@ -71,6 +72,7 @@ android {
|
|||
beta {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
manifestPlaceholders = [appName: BETA_NAME]
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
debuggable false
|
||||
signingConfig signingConfigs.debug
|
||||
|
|
@ -80,6 +82,7 @@ android {
|
|||
|
||||
debug {
|
||||
minifyEnabled false
|
||||
manifestPlaceholders = [appName: RELEASE_NAME]
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
debuggable true
|
||||
signingConfig signingConfigs.debug
|
||||
|
|
@ -87,6 +90,7 @@ android {
|
|||
|
||||
benchmark {
|
||||
initWith release
|
||||
manifestPlaceholders = [appName: RELEASE_NAME]
|
||||
proguardFiles 'baseline-profiles-rules.pro'
|
||||
debuggable false
|
||||
matchingFallbacks ['release']
|
||||
|
|
|
|||
|
|
@ -15,16 +15,20 @@
|
|||
<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.SCHEDULE_EXACT_ALARM"
|
||||
android:maxSdkVersion="32"/>
|
||||
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
|
||||
|
||||
|
||||
<application
|
||||
android:name=".App"
|
||||
android:allowBackup="false"
|
||||
android:configChanges="layoutDirection|locale"
|
||||
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|locale"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:extractNativeLibs="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:label="${appName}"
|
||||
android:localeConfig="@xml/locales_config"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
|
|
@ -38,14 +42,14 @@
|
|||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
|
||||
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|locale"
|
||||
android:exported="true"
|
||||
android:windowSoftInputMode="adjustPan">
|
||||
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".receiver.ShareActivity"
|
||||
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
|
||||
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|locale"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:launchMode="singleInstance"
|
||||
|
|
@ -71,7 +75,7 @@
|
|||
</activity>
|
||||
<activity
|
||||
android:name=".receiver.ShareFileActivity"
|
||||
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
|
||||
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|locale"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:launchMode="singleInstance"
|
||||
|
|
@ -92,7 +96,7 @@
|
|||
</activity>
|
||||
<activity
|
||||
android:name=".ui.more.TerminalActivity"
|
||||
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
|
||||
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|locale"
|
||||
android:exported="true"
|
||||
android:label="@string/terminal"
|
||||
android:parentActivityName=".MainActivity">
|
||||
|
|
@ -122,7 +126,7 @@
|
|||
android:enabled="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
|
||||
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|locale"
|
||||
android:targetActivity=".MainActivity"
|
||||
android:windowSoftInputMode="adjustPan"
|
||||
android:exported="true">
|
||||
|
|
@ -144,7 +148,7 @@
|
|||
android:exported="true"
|
||||
android:icon="@mipmap/ic_launcher_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:windowSoftInputMode="adjustPan">
|
||||
<intent-filter>
|
||||
|
|
@ -164,7 +168,7 @@
|
|||
android:exported="true"
|
||||
android:icon="@mipmap/ic_launcher_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:windowSoftInputMode="adjustPan">
|
||||
<intent-filter>
|
||||
|
|
@ -180,7 +184,7 @@
|
|||
|
||||
<activity
|
||||
android:name=".ui.more.WebViewActivity"
|
||||
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
|
||||
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|locale"
|
||||
android:exported="true"
|
||||
android:parentActivityName=".MainActivity"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
|
|
@ -193,10 +197,12 @@
|
|||
|
||||
<receiver android:name=".receiver.CancelDownloadNotificationReceiver" />
|
||||
<receiver android:name=".receiver.PauseDownloadNotificationReceiver" />
|
||||
<receiver android:name=".receiver.AlarmStartReceiver" />
|
||||
<receiver android:name=".receiver.AlarmStopReceiver" />
|
||||
|
||||
<activity
|
||||
android:name=".receiver.ResumeActivity"
|
||||
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
|
||||
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|locale"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:launchMode="singleInstance"
|
||||
|
|
|
|||
|
|
@ -12,33 +12,25 @@ import android.graphics.drawable.ColorDrawable
|
|||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowInsets
|
||||
import android.widget.CheckBox
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.forEach
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.FragmentContainerView
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavDirections
|
||||
import androidx.navigation.fragment.FragmentNavigatorExtras
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.navOptions
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||
|
|
@ -60,7 +52,6 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
import java.io.Reader
|
||||
|
|
@ -157,7 +148,6 @@ class MainActivity : BaseActivity() {
|
|||
activeDownloadsBadge.number = it
|
||||
}
|
||||
}
|
||||
window.navigationBarColor = SurfaceColors.SURFACE_2.getColor(this)
|
||||
}
|
||||
if (navigationView is NavigationView){
|
||||
(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.reflect.TypeToken
|
||||
import java.lang.reflect.Type
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
class Converters {
|
||||
|
|
|
|||
|
|
@ -7,12 +7,15 @@ import kotlinx.coroutines.flow.Flow
|
|||
|
||||
@Dao
|
||||
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>
|
||||
|
||||
@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>>
|
||||
|
||||
@Query("SELECT * FROM logs WHERE id=:id LIMIT 1")
|
||||
fun getLogFlowByID(id: Long) : Flow<LogItem>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
suspend fun insert(item: LogItem) : Long
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@ class LogRepository(private val logDao: LogDao) {
|
|||
return logDao.getAllLogs()
|
||||
}
|
||||
|
||||
fun getLogFlowByID(id: Long) : Flow<LogItem> {
|
||||
return logDao.getLogFlowByID(id)
|
||||
}
|
||||
|
||||
|
||||
suspend fun insert(item: LogItem) : Long{
|
||||
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.util.FileUtil
|
||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||
import com.deniscerri.ytdlnis.work.AlarmScheduler
|
||||
import com.deniscerri.ytdlnis.work.DownloadWorker
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
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 {
|
||||
val context = App.instance
|
||||
val alarmScheduler = AlarmScheduler(context)
|
||||
val activeAndQueuedDownloads = repository.getActiveAndQueuedDownloads()
|
||||
val queuedItems = mutableListOf<DownloadItem>()
|
||||
var exists = false
|
||||
|
||||
//if scheduler is on
|
||||
val useScheduler = sharedPreferences.getBoolean("use_scheduler", false)
|
||||
if (useScheduler && !alarmScheduler.isDuringTheScheduledTime()){
|
||||
alarmScheduler.schedule()
|
||||
}
|
||||
|
||||
items.forEach {
|
||||
if (it.status != DownloadRepository.Status.Paused.toString()) it.status = DownloadRepository.Status.Queued.toString()
|
||||
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>) {
|
||||
|
|
@ -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)){
|
||||
|
||||
val currentTime = System.currentTimeMillis()
|
||||
var delay = 1000L
|
||||
var delay = 0L
|
||||
if (queuedItems.isNotEmpty()){
|
||||
delay = if (queuedItems[0].downloadStartTime != 0L){
|
||||
queuedItems[0].downloadStartTime.minus(currentTime)
|
||||
} 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()
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.deniscerri.ytdlnis.database.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
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.LogRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
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{
|
||||
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()
|
||||
val cancel = loadingBottomSheet.findViewById<TextView>(R.id.cancel)
|
||||
cancel!!.setOnClickListener {
|
||||
resultViewModel.deleteAll()
|
||||
this.finish()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,10 +8,12 @@ import androidx.core.view.WindowInsetsCompat
|
|||
import androidx.core.view.updatePadding
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.util.ThemeUtil
|
||||
import com.google.android.material.elevation.SurfaceColors
|
||||
|
||||
open class BaseActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
ThemeUtil.updateTheme(this)
|
||||
window.navigationBarColor = SurfaceColors.SURFACE_2.getColor(this)
|
||||
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.BottomSheetDialogFragment
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
|
@ -78,7 +80,10 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
|
|||
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
|
||||
lifecycleScope.launch{
|
||||
withContext(Dispatchers.IO){
|
||||
|
|
|
|||
|
|
@ -1,30 +1,18 @@
|
|||
package com.deniscerri.ytdlnis.ui.downloadcard
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.Window
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.AutoCompleteTextView
|
||||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
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.lifecycle.ViewModelProvider
|
||||
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.Type
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
||||
import com.deniscerri.ytdlnis.databinding.ExtraCommandsBottomSheetBinding
|
||||
import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||
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.chip.Chip
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
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.tabs.TabLayout
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
|
|
@ -80,7 +81,6 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
tabLayout = view.findViewById(R.id.download_tablayout)
|
||||
viewPager2 = view.findViewById(R.id.download_viewpager)
|
||||
|
||||
|
|
@ -88,6 +88,7 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
|
|||
isNestedScrollingEnabled = false
|
||||
overScrollMode = View.OVER_SCROLL_NEVER
|
||||
}
|
||||
|
||||
downloadAudioFragment = DownloadAudioFragment(resultItem, downloadItem)
|
||||
downloadVideoFragment = DownloadVideoFragment(resultItem, downloadItem)
|
||||
val fragments = mutableListOf(downloadAudioFragment, downloadVideoFragment)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import android.content.Intent
|
|||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
|
|
@ -185,8 +186,7 @@ class DownloadCommandFragment(private val resultItem: ResultItem, private var cu
|
|||
},
|
||||
editSelectedClicked =
|
||||
{
|
||||
var current = templates.find { it.id == commandTemplateCard.findViewById<android.widget.TextView>(
|
||||
R.id.id).toString().toLong() }
|
||||
var current = templates.find { it.id == commandTemplateCard.tag }
|
||||
if (current == null) current =
|
||||
CommandTemplate(
|
||||
0,
|
||||
|
|
@ -197,7 +197,8 @@ class DownloadCommandFragment(private val resultItem: ResultItem, private var cu
|
|||
UiUtil.showCommandTemplateCreationOrUpdatingSheet(current, requireActivity(), viewLifecycleOwner, commandTemplateViewModel) {
|
||||
templates.add(it)
|
||||
chosenCommandView.editText!!.setText(it.content)
|
||||
downloadItem.format = com.deniscerri.ytdlnis.database.models.Format(
|
||||
populateCommandCard(commandTemplateCard, it)
|
||||
downloadItem.format = Format(
|
||||
it.title,
|
||||
"",
|
||||
"",
|
||||
|
|
|
|||
|
|
@ -2,31 +2,18 @@ package com.deniscerri.ytdlnis.ui.downloadcard
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.icu.text.IDNA.Info
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.Window
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.AutoCompleteTextView
|
||||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
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.lifecycle.ViewModelProvider
|
||||
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.InfoUtil
|
||||
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.chip.Chip
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import androidx.recyclerview.widget.GridLayoutManager
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.WorkQuery
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.adapter.ActiveDownloadAdapter
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
|
|
@ -131,7 +132,7 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
|||
}
|
||||
|
||||
WorkManager.getInstance(requireContext())
|
||||
.getWorkInfosByTagLiveData("download")
|
||||
.getWorkInfosLiveData(WorkQuery.fromStates(WorkInfo.State.RUNNING))
|
||||
.observe(viewLifecycleOwner){ list ->
|
||||
list.forEach {work ->
|
||||
if (work == null) return@forEach
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import android.graphics.Color
|
|||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.FileObserver
|
||||
import android.os.PersistableBundle
|
||||
import android.util.Log
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
|
|
@ -76,7 +77,7 @@ class TerminalActivity : BaseActivity() {
|
|||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_terminal)
|
||||
|
||||
downloadID = System.currentTimeMillis().toInt() % 100000
|
||||
downloadID = savedInstanceState?.getInt("downloadID") ?: (System.currentTimeMillis().toInt() % 100000)
|
||||
downloadFile = File(cacheDir.absolutePath + "/$downloadID.txt")
|
||||
if (! downloadFile.exists()) downloadFile.createNewFile()
|
||||
imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
|
|
@ -233,6 +234,22 @@ class TerminalActivity : BaseActivity() {
|
|||
|
||||
input?.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() {
|
||||
|
|
|
|||
|
|
@ -25,9 +25,11 @@ import com.neo.highlight.util.listener.HighlightTextWatcher
|
|||
import com.neo.highlight.util.scheme.ColorScheme
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.util.regex.Pattern
|
||||
import kotlin.math.log
|
||||
|
||||
|
||||
class DownloadLogFragment : Fragment() {
|
||||
|
|
@ -78,7 +80,7 @@ class DownloadLogFragment : Fragment() {
|
|||
|
||||
|
||||
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{
|
||||
content.text = logItem?.content
|
||||
content.scrollTo(0, content.height)
|
||||
|
|
@ -86,9 +88,8 @@ class DownloadLogFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val logItem = logViewModel.getItemById(id!!)
|
||||
val logItem = logViewModel.getItemById(id)
|
||||
topAppBar.title = logItem.title
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,12 +26,15 @@ import com.deniscerri.ytdlnis.R
|
|||
import com.deniscerri.ytdlnis.adapter.DownloadLogsAdapter
|
||||
import com.deniscerri.ytdlnis.database.models.LogItem
|
||||
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.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
||||
class DownloadLogListFragment : Fragment(), DownloadLogsAdapter.OnItemClickListener {
|
||||
|
|
@ -69,6 +72,8 @@ class DownloadLogListFragment : Fragment(), DownloadLogsAdapter.OnItemClickListe
|
|||
)
|
||||
recyclerView = view.findViewById(R.id.logs_recyclerview)
|
||||
recyclerView.layoutManager = LinearLayoutManager(context)
|
||||
recyclerView.enableFastScroll()
|
||||
recyclerView.forceFastScrollMode()
|
||||
recyclerView.adapter = downloadLogAdapter
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
if (preferences.getBoolean("swipe_gestures", true)){
|
||||
|
|
@ -230,14 +235,18 @@ class DownloadLogListFragment : Fragment(), DownloadLogsAdapter.OnItemClickListe
|
|||
val position = viewHolder.bindingAdapterPosition
|
||||
when (direction) {
|
||||
ItemTouchHelper.LEFT -> {
|
||||
val deletedItem = items[position]
|
||||
logViewModel.delete(deletedItem)
|
||||
Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
|
||||
.setAction(getString(R.string.undo)) {
|
||||
lifecycleScope.launch(Dispatchers.IO){
|
||||
logViewModel.insert(deletedItem)
|
||||
}
|
||||
}.show()
|
||||
lifecycleScope.launch {
|
||||
val deletedItem = withContext(Dispatchers.IO){
|
||||
logViewModel.getItemById(items[position].id)
|
||||
}
|
||||
logViewModel.delete(deletedItem)
|
||||
Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
|
||||
.setAction(getString(R.string.undo)) {
|
||||
lifecycleScope.launch(Dispatchers.IO){
|
||||
logViewModel.insert(deletedItem)
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
package com.deniscerri.ytdlnis.ui.more.settings
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.content.DialogInterface
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.ListView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.MultiSelectListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.deniscerri.ytdlnis.R
|
||||
|
|
@ -46,6 +45,24 @@ abstract class BaseSettingsFragment : PreferenceFragmentCompat() {
|
|||
.setNegativeButton(R.string.cancel, null)
|
||||
.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 -> {
|
||||
val binding = TextinputBinding.inflate(layoutInflater)
|
||||
binding.urlEdittext.setText(preference.text)
|
||||
|
|
|
|||
|
|
@ -1,45 +1,70 @@
|
|||
package com.deniscerri.ytdlnis.ui.more.settings
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context.POWER_SERVICE
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.PowerManager
|
||||
import android.provider.Settings
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.SeekBarPreference
|
||||
import com.deniscerri.ytdlnis.MainActivity
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
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() {
|
||||
override val title: Int = R.string.downloads
|
||||
|
||||
private var concurrentDownloads: SeekBarPreference? = null
|
||||
private var ignoreBatteryOptimization: Preference? = null
|
||||
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.downloading_preferences, rootKey)
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
|
||||
ignoreBatteryOptimization = findPreference("ignore_battery")
|
||||
val packageName: String = requireContext().packageName
|
||||
val pm = requireContext().applicationContext.getSystemService(POWER_SERVICE) as PowerManager
|
||||
if (pm.isIgnoringBatteryOptimizations(packageName)) {
|
||||
ignoreBatteryOptimization!!.isVisible = false
|
||||
val useScheduler = findPreference<SwitchPreferenceCompat>("use_scheduler")
|
||||
val scheduleStart = findPreference<Preference>("schedule_start")
|
||||
scheduleStart?.summary = preferences.getString("schedule_start", "00:00")
|
||||
val scheduleEnd = findPreference<Preference>("schedule_end")
|
||||
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")
|
||||
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
|
||||
scheduleStart?.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_start",formattedTime).apply()
|
||||
scheduleStart.summary = formattedTime
|
||||
|
||||
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 commandPath: Preference? = null
|
||||
private var accessAllFiles : Preference? = null
|
||||
private var cacheDownloads : Preference? = null
|
||||
private var audioFilenameTemplate : EditTextPreference? = null
|
||||
private var videoFilenameTemplate : EditTextPreference? = null
|
||||
private var clearCache: Preference? = null
|
||||
|
|
@ -46,6 +47,7 @@ class FolderSettingsFragment : BaseSettingsFragment() {
|
|||
videoPath = findPreference("video_path")
|
||||
commandPath = findPreference("command_path")
|
||||
accessAllFiles = findPreference("access_all_files")
|
||||
cacheDownloads = findPreference("cache_downloads")
|
||||
videoFilenameTemplate = findPreference("file_name_template")
|
||||
audioFilenameTemplate = findPreference("file_name_template_audio")
|
||||
clearCache = findPreference("clear_cache")
|
||||
|
|
@ -64,6 +66,10 @@ class FolderSettingsFragment : BaseSettingsFragment() {
|
|||
if((VERSION.SDK_INT >= 30 && Environment.isExternalStorageManager()) ||
|
||||
VERSION.SDK_INT < 30) {
|
||||
accessAllFiles!!.isVisible = false
|
||||
cacheDownloads!!.isEnabled = true
|
||||
}else{
|
||||
editor.putBoolean("cache_downloads", false).apply()
|
||||
cacheDownloads!!.isEnabled = false
|
||||
}
|
||||
|
||||
editor.apply()
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
package com.deniscerri.ytdlnis.ui.more.settings
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build.VERSION
|
||||
import android.os.Build.VERSION_CODES
|
||||
import android.os.Bundle
|
||||
import android.os.PowerManager
|
||||
import android.provider.Settings
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.os.LocaleListCompat
|
||||
|
|
@ -126,5 +130,22 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
|
|||
}
|
||||
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.copyFolderTo
|
||||
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.R
|
||||
import com.yausername.youtubedl_android.YoutubeDLRequest
|
||||
|
|
@ -89,8 +91,47 @@ object FileUtil {
|
|||
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 dst = DocumentFile.fromTreeUri(context, destDir.toUri())
|
||||
|
|
@ -124,23 +165,7 @@ object FileUtil {
|
|||
runCatching {
|
||||
it.walkTopDown().forEach { f ->
|
||||
if (f.isDirectory) 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()
|
||||
|
||||
val destUri = moveFileInputStream(it, context, dst) ?: return@forEach
|
||||
fileList.add(DocumentFile.fromSingleUri(context, destUri)!!.getAbsolutePath(context))
|
||||
}
|
||||
|
||||
|
|
@ -153,26 +178,11 @@ object FileUtil {
|
|||
}
|
||||
}else{
|
||||
withContext(Dispatchers.IO){
|
||||
curr.copyFileTo(context, dst!!, callback = object : FileCallback() {
|
||||
curr.moveFileTo(context, dst!!, callback = object : FileCallback() {
|
||||
override fun onFailed(errorCode: ErrorCode) {
|
||||
//if its usb?
|
||||
runCatching {
|
||||
val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(it.extension) ?: "*/*"
|
||||
|
||||
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()
|
||||
|
||||
val destUri = moveFileInputStream(it, context, dst) ?: return
|
||||
fileList.add(DocumentFile.fromSingleUri(context, destUri)!!.getAbsolutePath(context))
|
||||
it.delete()
|
||||
}
|
||||
|
|
@ -205,42 +215,6 @@ object FileUtil {
|
|||
}
|
||||
}catch (e: Exception) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
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 {
|
||||
val paths = files.sortedByDescending { File(it).length() }
|
||||
runCatching {MediaScannerConnection.scanFile(context, paths.toTypedArray(), null, null) }
|
||||
|
|
@ -259,7 +254,7 @@ object FileUtil {
|
|||
e.printStackTrace()
|
||||
}
|
||||
|
||||
return listOf(context.getString(R.string.unfound_file))
|
||||
return listOf()
|
||||
}
|
||||
|
||||
fun getCachePath(context: Context) : String {
|
||||
|
|
|
|||
|
|
@ -133,13 +133,14 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
@Throws(JSONException::class)
|
||||
fun getYoutubeVideo(url: String): List<ResultItem?> {
|
||||
val theURL = url.replace("\\?list.*".toRegex(), "")
|
||||
return try {
|
||||
val id = getIDFromYoutubeURL(url)
|
||||
val id = getIDFromYoutubeURL(theURL)
|
||||
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){
|
||||
val v = getFromYTDL(url)
|
||||
v.forEach { it!!.url = url }
|
||||
val v = getFromYTDL(theURL)
|
||||
v.forEach { it!!.url = theURL }
|
||||
v
|
||||
}
|
||||
}
|
||||
|
|
@ -519,8 +520,11 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
YoutubeDL.getInstance().execute(request){ progress, _, line ->
|
||||
try{
|
||||
val listOfStrings = JSONArray(line)
|
||||
progress(parseYTDLFormats(listOfStrings))
|
||||
if (line.isNotBlank()){
|
||||
val listOfStrings = JSONArray(line)
|
||||
progress(parseYTDLFormats(listOfStrings))
|
||||
}
|
||||
|
||||
}catch (e: Exception){
|
||||
progress(emptyList())
|
||||
}
|
||||
|
|
@ -932,16 +936,38 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
fun buildYoutubeDLRequest(downloadItem: DownloadItem) : YoutubeDLRequest{
|
||||
val cacheDir = FileUtil.getCachePath(context)
|
||||
val tempFileDir = File(cacheDir, downloadItem.id.toString())
|
||||
tempFileDir.delete()
|
||||
tempFileDir.mkdirs()
|
||||
fun getFilePaths(request: YoutubeDLRequest) : List<String>{
|
||||
return try{
|
||||
request.addOption("--print", "filename")
|
||||
val res = YoutubeDL.getInstance().execute(request)
|
||||
val results: Array<String?> = try {
|
||||
val lineSeparator = System.getProperty("line.separator")
|
||||
res.out.split(lineSeparator!!).toTypedArray()
|
||||
} catch (e: Exception) {
|
||||
arrayOf(res.out)
|
||||
}
|
||||
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 request = YoutubeDLRequest(url)
|
||||
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)
|
||||
if (aria2) {
|
||||
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()){
|
||||
request.addOption("--split-chapters")
|
||||
|
|
@ -1270,7 +1296,7 @@ class InfoUtil(private val context: Context) {
|
|||
request.addOption("--ppa", "ffmpeg:-an")
|
||||
}
|
||||
|
||||
request.addOption("-P", tempFileDir.absolutePath)
|
||||
request.addOption("-P", downDir.absolutePath)
|
||||
|
||||
if (downloadItem.videoPreferences.splitByChapters && downloadItem.downloadSections.isBlank()){
|
||||
request.addOption("--split-chapters")
|
||||
|
|
@ -1283,13 +1309,13 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
}
|
||||
DownloadViewModel.Type.command -> {
|
||||
request.addOption("-P", downDir.absolutePath)
|
||||
request.addOption(
|
||||
"--config-locations",
|
||||
File(context.cacheDir.absolutePath + "/config[${downloadItem.id}].txt").apply {
|
||||
writeText(downloadItem.format.format_note)
|
||||
}.absolutePath
|
||||
)
|
||||
request.addOption("-P", tempFileDir.absolutePath)
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import androidx.core.content.ContextCompat
|
|||
import androidx.core.content.FileProvider
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.preference.PreferenceManager
|
||||
|
|
@ -117,7 +118,7 @@ object UiUtil {
|
|||
fun populateCommandCard(card: MaterialCardView, item: CommandTemplate){
|
||||
card.findViewById<TextView>(R.id.title).text = item.title
|
||||
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){
|
||||
|
|
@ -279,51 +280,62 @@ object UiUtil {
|
|||
}
|
||||
|
||||
|
||||
fun openFileIntent(fragmentContext: Context, downloadPath: String) {
|
||||
val file = File(downloadPath)
|
||||
val uri = FileProvider.getUriForFile(
|
||||
fragmentContext,
|
||||
fragmentContext.packageName + ".fileprovider",
|
||||
file
|
||||
)
|
||||
val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(file.extension)
|
||||
val i = Intent(Intent.ACTION_VIEW)
|
||||
i.setDataAndType(uri, mime)
|
||||
i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
try {
|
||||
fragmentContext.startActivity(i)
|
||||
}catch (e: Exception){
|
||||
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
fragmentContext.startActivity(i)
|
||||
fun openFileIntent(context: Context, downloadPath: String) {
|
||||
val uri = downloadPath.runCatching {
|
||||
DocumentFile.fromSingleUri(context, Uri.parse(downloadPath)).run{
|
||||
if (this?.exists() == true){
|
||||
this.uri
|
||||
}else if (File(this@runCatching).exists()){
|
||||
FileProvider.getUriForFile(context, context.packageName + ".fileprovider",
|
||||
File(this@runCatching))
|
||||
}else null
|
||||
}
|
||||
}.getOrNull()
|
||||
|
||||
if (uri == null){
|
||||
Toast.makeText(context, "Error opening file!", Toast.LENGTH_SHORT).show()
|
||||
}else{
|
||||
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()
|
||||
paths.forEach {
|
||||
val file = File(it)
|
||||
if (! file.exists()) return@forEach
|
||||
val uri = FileProvider.getUriForFile(
|
||||
fragmentContext,
|
||||
fragmentContext.packageName + ".fileprovider",
|
||||
file
|
||||
)
|
||||
uris.add(uri)
|
||||
paths.runCatching {
|
||||
this.forEach {path ->
|
||||
val uri = DocumentFile.fromSingleUri(context, Uri.parse(path)).run{
|
||||
if (this?.exists() == true){
|
||||
this.uri
|
||||
}else if (File(path).exists()){
|
||||
FileProvider.getUriForFile(context, context.packageName + ".fileprovider",
|
||||
File(path))
|
||||
}else null
|
||||
}
|
||||
if (uri != null) uris.add(uri)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
val shareIntent: Intent = Intent().apply {
|
||||
action = Intent.ACTION_SEND_MULTIPLE
|
||||
putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris)
|
||||
type = "*/*"
|
||||
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
|
||||
}
|
||||
try {
|
||||
fragmentContext.startActivity(Intent.createChooser(shareIntent, fragmentContext.getString(R.string.share)))
|
||||
}catch (e: Exception){
|
||||
val intent = Intent.createChooser(shareIntent, fragmentContext.getString(R.string.share))
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
fragmentContext.startActivity(intent)
|
||||
if (uris.isEmpty()){
|
||||
Toast.makeText(context, "Error sharing files!", Toast.LENGTH_SHORT).show()
|
||||
}else{
|
||||
Intent().apply {
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
action = Intent.ACTION_SEND_MULTIPLE
|
||||
putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris)
|
||||
type = if (uris.size == 1) uris[0].let { context.contentResolver.getType(it) } ?: "media/*" else "*/*"
|
||||
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
|
||||
}.run{
|
||||
context.startActivity(Intent.createChooser(this, context.getString(R.string.share)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -361,6 +373,24 @@ object UiUtil {
|
|||
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(
|
||||
item: DownloadItem,
|
||||
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.withContext
|
||||
import java.io.File
|
||||
import java.util.Calendar
|
||||
import kotlin.random.Random
|
||||
|
||||
|
||||
|
|
@ -53,6 +54,7 @@ class DownloadWorker(
|
|||
val resultDao = dbManager.resultDao
|
||||
val logRepo = LogRepository(dbManager.logDao)
|
||||
val handler = Handler(Looper.getMainLooper())
|
||||
val alarmScheduler = AlarmScheduler(context)
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
val time = System.currentTimeMillis() + 6000
|
||||
val queuedItems = dao.getQueuedDownloadsThatAreNotScheduledChunked(time)
|
||||
|
|
@ -75,8 +77,14 @@ class DownloadWorker(
|
|||
}
|
||||
|
||||
val running = ArrayList(runningYTDLInstances)
|
||||
val useScheduler = sharedPreferences.getBoolean("use_scheduler", false)
|
||||
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 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"
|
||||
}
|
||||
else { "" } +
|
||||
"Command: ${infoUtil.parseYTDLRequestString(request)} ${downloadItem.extraCommands}\n\n",
|
||||
"Command:\n ${infoUtil.parseYTDLRequestString(request)} ${downloadItem.extraCommands}\n\n",
|
||||
downloadItem.format,
|
||||
downloadItem.type,
|
||||
System.currentTimeMillis(),
|
||||
|
|
@ -147,33 +155,47 @@ class DownloadWorker(
|
|||
}
|
||||
}.onSuccess {
|
||||
runningYTDLInstances.remove(downloadItem.id)
|
||||
FileUtil.deleteConfigFiles(request)
|
||||
|
||||
val wasQuickDownloaded = updateDownloadItem(downloadItem, infoUtil, dao, resultDao)
|
||||
runBlocking {
|
||||
var finalPaths : List<String>?
|
||||
//move file from internal to set download directory
|
||||
setProgressAsync(workDataOf("progress" to 100, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id))
|
||||
try {
|
||||
finalPaths = withContext(Dispatchers.IO){
|
||||
FileUtil.moveFile(tempFileDir.absoluteFile,context, downloadLocation, keepCache){ p ->
|
||||
setProgressAsync(workDataOf("progress" to p, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id))
|
||||
}
|
||||
}
|
||||
|
||||
if (finalPaths.isNotEmpty()){
|
||||
setProgressAsync(workDataOf("progress" to 100, "output" to "Moved file to $downloadLocation", "id" to downloadItem.id))
|
||||
}else{
|
||||
//if there was no cache used
|
||||
if (!sharedPreferences.getBoolean("cache_downloads", true) && File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite()){
|
||||
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))
|
||||
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
|
||||
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:padding="15dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:visibility="gone"
|
||||
android:id="@+id/id"
|
||||
android:layout_height="0dp"
|
||||
tools:ignore="MissingConstraints" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
|
|
|
|||
|
|
@ -57,9 +57,9 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
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_constraintTop_toTopOf="parent" />
|
||||
app:layout_constraintTop_toTopOf="@id/bottomsheet_download_button" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/bottomsheet_download_button"
|
||||
|
|
|
|||
|
|
@ -37,11 +37,9 @@
|
|||
android:paddingBottom="150dp"
|
||||
>
|
||||
|
||||
<LinearLayout
|
||||
<HorizontalScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusableInTouchMode="true"
|
||||
android:orientation="vertical">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/content"
|
||||
|
|
@ -49,11 +47,12 @@
|
|||
android:fontFamily="monospace"
|
||||
android:textSize="15sp"
|
||||
android:gravity="bottom"
|
||||
android:layout_width="match_parent"
|
||||
android:scrollbars="vertical|horizontal"
|
||||
android:layout_width="wrap_content"
|
||||
android:textIsSelectable="true"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
</HorizontalScrollView>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,24 @@
|
|||
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:label="@string/more"
|
||||
android:id="@+id/main_nav"
|
||||
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
|
||||
android:id="@+id/homeFragment"
|
||||
android:name="com.deniscerri.ytdlnis.ui.HomeFragment"
|
||||
|
|
@ -46,17 +61,15 @@
|
|||
<action
|
||||
android:id="@+id/action_moreFragment_to_downloadLogListFragment2"
|
||||
app:destination="@id/downloadLogListFragment" />
|
||||
<action
|
||||
android:id="@+id/action_moreFragment_to_settingsNav"
|
||||
app:destination="@id/settingsNav" />
|
||||
</fragment>
|
||||
<activity
|
||||
android:id="@+id/terminalActivity"
|
||||
android:name="com.deniscerri.ytdlnis.ui.more.TerminalActivity"
|
||||
android:label="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
|
||||
android:id="@+id/cookiesFragment"
|
||||
android:name="com.deniscerri.ytdlnis.ui.more.CookiesFragment"
|
||||
|
|
@ -88,4 +101,52 @@
|
|||
android:id="@+id/resultCardDetailsDialog"
|
||||
android:name="com.deniscerri.ytdlnis.ui.downloadcard.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>
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">YTDLnis</string>
|
||||
<string name="search">Arama</string>
|
||||
<string name="remove_all">Hepsini Kaldır</string>
|
||||
<string name="remove_results">Sonuçları Temizle</string>
|
||||
|
|
|
|||
|
|
@ -735,6 +735,7 @@
|
|||
<item>@string/in_queue</item>
|
||||
<item>@string/cancelled</item>
|
||||
<item>@string/errored</item>
|
||||
<item>@string/saved</item>
|
||||
<item>@string/cookies</item>
|
||||
<item>@string/command_templates</item>
|
||||
<item>@string/shortcuts</item>
|
||||
|
|
@ -747,6 +748,7 @@
|
|||
<item>queued</item>
|
||||
<item>cancelled</item>
|
||||
<item>errored</item>
|
||||
<item>saved</item>
|
||||
<item>cookies</item>
|
||||
<item>templates</item>
|
||||
<item>shortcuts</item>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
<resources>
|
||||
<string name="worst_quality">Worst Quality</string>
|
||||
<string name="best_quality">Best Quality</string>
|
||||
<string name="app_name" translatable="false">YTDLnis</string>
|
||||
<string name="search">Search</string>
|
||||
<string name="remove_all">Remove All</string>
|
||||
<string name="remove_results">Clear Results</string>
|
||||
|
|
@ -304,4 +303,8 @@
|
|||
<string name="update_formats_background">Update Formats in Background</string>
|
||||
<string name="auto_update_ytdlp">Auto-Update yt-dlp</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>
|
||||
|
|
@ -3,111 +3,134 @@
|
|||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="false"
|
||||
android:icon="@drawable/ic_incognito"
|
||||
android:key="incognito"
|
||||
app:summary="@string/incognito_summary"
|
||||
app:title="@string/incognito" />
|
||||
<PreferenceCategory app:title="@string/downloading">
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="false"
|
||||
android:icon="@drawable/ic_incognito"
|
||||
android:key="incognito"
|
||||
app:summary="@string/incognito_summary"
|
||||
app:title="@string/incognito" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="true"
|
||||
android:icon="@drawable/ic_card"
|
||||
android:key="download_card"
|
||||
app:summary="@string/download_card_summary"
|
||||
app:title="@string/show_download_card" />
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="true"
|
||||
android:icon="@drawable/ic_card"
|
||||
android:key="download_card"
|
||||
app:summary="@string/download_card_summary"
|
||||
app:title="@string/show_download_card" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="false"
|
||||
android:icon="@drawable/ic_speed"
|
||||
android:key="quick_download"
|
||||
app:summary="@string/quick_download_summary"
|
||||
app:title="@string/quick_download" />
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="false"
|
||||
android:icon="@drawable/ic_speed"
|
||||
android:key="quick_download"
|
||||
app:summary="@string/quick_download_summary"
|
||||
app:title="@string/quick_download" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="true"
|
||||
android:icon="@drawable/metered_networks"
|
||||
android:key="metered_networks"
|
||||
app:summary="@string/download_over_metered_networks_summary"
|
||||
app:title="@string/download_over_metered_networks" />
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="true"
|
||||
android:icon="@drawable/metered_networks"
|
||||
android:key="metered_networks"
|
||||
app:summary="@string/download_over_metered_networks_summary"
|
||||
app:title="@string/download_over_metered_networks" />
|
||||
|
||||
|
||||
<EditTextPreference
|
||||
android:icon="@drawable/baseline_network_locked_24"
|
||||
app:key="proxy"
|
||||
app:defaultValue=""
|
||||
app:summary="@string/socks5_proxy_summary"
|
||||
app:title="@string/socks5_proxy" />
|
||||
<EditTextPreference
|
||||
android:icon="@drawable/baseline_network_locked_24"
|
||||
app:key="proxy"
|
||||
app:defaultValue=""
|
||||
app:summary="@string/socks5_proxy_summary"
|
||||
app:title="@string/socks5_proxy" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="video"
|
||||
android:entries="@array/download_types"
|
||||
android:entryValues="@array/download_types_values"
|
||||
android:icon="@drawable/ic_download_type"
|
||||
app:key="preferred_download_type"
|
||||
app:summary="@string/preferred_download_type_summary"
|
||||
app:title="@string/preferred_download_type" />
|
||||
<ListPreference
|
||||
android:defaultValue="video"
|
||||
android:entries="@array/download_types"
|
||||
android:entryValues="@array/download_types_values"
|
||||
android:icon="@drawable/ic_download_type"
|
||||
app:key="preferred_download_type"
|
||||
app:summary="@string/preferred_download_type_summary"
|
||||
app:title="@string/preferred_download_type" />
|
||||
|
||||
<SeekBarPreference
|
||||
app:dependency="aria2"
|
||||
android:defaultValue="3"
|
||||
android:icon="@drawable/ic_lines"
|
||||
android:max="25"
|
||||
app:key="concurrent_fragments"
|
||||
app:min="1"
|
||||
app:showSeekBarValue="true"
|
||||
android:dependency="aria2"
|
||||
app:summary="@string/concurrent_fragments_summary"
|
||||
app:title="@string/concurrent_fragments" />
|
||||
<SeekBarPreference
|
||||
app:dependency="aria2"
|
||||
android:defaultValue="3"
|
||||
android:icon="@drawable/ic_lines"
|
||||
android:max="25"
|
||||
app:key="concurrent_fragments"
|
||||
app:min="1"
|
||||
app:showSeekBarValue="true"
|
||||
android:dependency="aria2"
|
||||
app:summary="@string/concurrent_fragments_summary"
|
||||
app:title="@string/concurrent_fragments" />
|
||||
|
||||
<SeekBarPreference
|
||||
android:defaultValue="1"
|
||||
android:icon="@drawable/ic_concurrent_downloads"
|
||||
android:max="10"
|
||||
app:key="concurrent_downloads"
|
||||
app:min="1"
|
||||
app:showSeekBarValue="true"
|
||||
app:summary="@string/concurrent_downloads_summary"
|
||||
app:title="@string/concurrent_downloads" />
|
||||
<SeekBarPreference
|
||||
android:defaultValue="1"
|
||||
android:icon="@drawable/ic_concurrent_downloads"
|
||||
android:max="10"
|
||||
app:key="concurrent_downloads"
|
||||
app:min="1"
|
||||
app:showSeekBarValue="true"
|
||||
app:summary="@string/concurrent_downloads_summary"
|
||||
app:title="@string/concurrent_downloads" />
|
||||
|
||||
<EditTextPreference
|
||||
android:icon="@drawable/ic_down"
|
||||
app:key="limit_rate"
|
||||
app:summary="@string/limit_rate_summary"
|
||||
app:title="@string/limit_rate" />
|
||||
<EditTextPreference
|
||||
android:icon="@drawable/ic_down"
|
||||
app:key="limit_rate"
|
||||
app:summary="@string/limit_rate_summary"
|
||||
app:title="@string/limit_rate" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="false"
|
||||
android:icon="@drawable/ic_speed"
|
||||
android:key="aria2"
|
||||
app:summary="@string/aria2_summary"
|
||||
android:disableDependentsState="true"
|
||||
app:title="Aria2" />
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="false"
|
||||
android:icon="@drawable/ic_speed"
|
||||
android:key="aria2"
|
||||
app:summary="@string/aria2_summary"
|
||||
android:disableDependentsState="true"
|
||||
app:title="Aria2" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="false"
|
||||
android:icon="@drawable/ic_baseline_file_open_24"
|
||||
android:key="log_downloads"
|
||||
app:summary="@string/log_downloads_summary"
|
||||
app:title="@string/log_downloads" />
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="false"
|
||||
android:icon="@drawable/ic_baseline_file_open_24"
|
||||
android:key="log_downloads"
|
||||
app:summary="@string/log_downloads_summary"
|
||||
app:title="@string/log_downloads" />
|
||||
|
||||
<EditTextPreference
|
||||
app:key="retries"
|
||||
app:title="@string/retries" />
|
||||
<EditTextPreference
|
||||
app:key="retries"
|
||||
app:title="@string/retries" />
|
||||
|
||||
<EditTextPreference
|
||||
app:key="fragment_retries"
|
||||
app:title="@string/fragment_retries" />
|
||||
<EditTextPreference
|
||||
app:key="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>
|
||||
|
|
@ -23,6 +23,25 @@
|
|||
android:summary="@string/access_all_directories_summary"
|
||||
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
|
||||
android:icon="@drawable/ic_textformat"
|
||||
|
|
@ -58,12 +77,4 @@
|
|||
android:summary="@string/clear_temporary_files_summary"
|
||||
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>
|
||||
|
|
@ -116,5 +116,11 @@
|
|||
android:icon="@drawable/baseline_share_24"
|
||||
android:key="show_terminal"
|
||||
app:title="@string/show_terminal" />
|
||||
|
||||
|
||||
<Preference
|
||||
app:icon="@drawable/ic_battery"
|
||||
app:key="ignore_battery"
|
||||
app:title="@string/ignore_battery_optimization" />
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
android:defaultValue="false"
|
||||
android:defaultValue="true"
|
||||
app:icon="@drawable/ic_nightly"
|
||||
app:key="nightly_ytdl"
|
||||
android:title="@string/update_ytdl_nightly"
|
||||
|
|
|
|||
|
|
@ -25,6 +25,9 @@ buildscript {
|
|||
media3_version = "1.1.1"
|
||||
agp_version = '8.1.0'
|
||||
agp_version1 = '7.4.2'
|
||||
|
||||
RELEASE_NAME = 'YTDLnis'
|
||||
BETA_NAME = 'YTDLnis [BETA]'
|
||||
}
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
|
|
|||
Loading…
Reference in a new issue