diff --git a/app/build.gradle b/app/build.gradle
index 60d63085..2c2a1c42 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -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']
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 94e2d12e..28965c07 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -15,16 +15,20 @@
+
+
+
@@ -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">
@@ -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">
@@ -180,7 +184,7 @@
@@ -193,10 +197,12 @@
+
+
- @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>
+ @Query("SELECT * FROM logs WHERE id=:id LIMIT 1")
+ fun getLogFlowByID(id: Long) : Flow
+
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(item: LogItem) : Long
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/LogRepository.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/LogRepository.kt
index 535c285b..3a476f36 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/LogRepository.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/LogRepository.kt
@@ -12,6 +12,10 @@ class LogRepository(private val logDao: LogDao) {
return logDao.getAllLogs()
}
+ fun getLogFlowByID(id: Long) : Flow {
+ return logDao.getLogFlowByID(id)
+ }
+
suspend fun insert(item: LogItem) : Long{
return logDao.insert(item)
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt
index d76accbc..61d06925 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt
@@ -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) = CoroutineScope(Dispatchers.IO).launch {
val context = App.instance
+ val alarmScheduler = AlarmScheduler(context)
val activeAndQueuedDownloads = repository.getActiveAndQueuedDownloads()
val queuedItems = mutableListOf()
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) {
@@ -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 {
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/LogViewModel.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/LogViewModel.kt
index 80f8f09f..40880889 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/LogViewModel.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/LogViewModel.kt
@@ -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 {
+ return repository.getLogFlowByID(id).asLiveData()
+ }
+
fun getItemById(id: Long): LogItem{
return repository.getItem(id)
}
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/receiver/AlarmStartReceiver.kt b/app/src/main/java/com/deniscerri/ytdlnis/receiver/AlarmStartReceiver.kt
new file mode 100644
index 00000000..4ba50807
--- /dev/null
+++ b/app/src/main/java/com/deniscerri/ytdlnis/receiver/AlarmStartReceiver.kt
@@ -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()
+ .addTag("download")
+ .setConstraints(workConstraints.build())
+ .setInitialDelay(1000L, TimeUnit.MILLISECONDS)
+
+ WorkManager.getInstance(p0!!).enqueueUniqueWork(
+ System.currentTimeMillis().toString(),
+ ExistingWorkPolicy.REPLACE,
+ workRequest.build()
+ )
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/receiver/AlarmStopReceiver.kt b/app/src/main/java/com/deniscerri/ytdlnis/receiver/AlarmStopReceiver.kt
new file mode 100644
index 00000000..d3074039
--- /dev/null
+++ b/app/src/main/java/com/deniscerri/ytdlnis/receiver/AlarmStopReceiver.kt
@@ -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()
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/receiver/ShareActivity.kt b/app/src/main/java/com/deniscerri/ytdlnis/receiver/ShareActivity.kt
index bbca7fe9..fd5ce020 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/receiver/ShareActivity.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/receiver/ShareActivity.kt
@@ -114,6 +114,7 @@ class ShareActivity : BaseActivity() {
loadingBottomSheet.show()
val cancel = loadingBottomSheet.findViewById(R.id.cancel)
cancel!!.setOnClickListener {
+ resultViewModel.deleteAll()
this.finish()
}
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/BaseActivity.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/BaseActivity.kt
index 0f8cb66a..52b2c011 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/ui/BaseActivity.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/BaseActivity.kt
@@ -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)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/ConfigureDownloadBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/ConfigureDownloadBottomSheetDialog.kt
index e2e5be0b..ac4fbfd2 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/ConfigureDownloadBottomSheetDialog.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/ConfigureDownloadBottomSheetDialog.kt
@@ -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){
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadAudioFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadAudioFragment.kt
index 8978aab3..8382616b 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadAudioFragment.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadAudioFragment.kt
@@ -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
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadBottomSheetDialog.kt
index 13e2a290..52d1e3cf 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadBottomSheetDialog.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadBottomSheetDialog.kt
@@ -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)
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadCommandFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadCommandFragment.kt
index d6303e4a..bd318036 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadCommandFragment.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadCommandFragment.kt
@@ -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(
- 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,
"",
"",
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadVideoFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadVideoFragment.kt
index 853e8ec4..16dfa4e6 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadVideoFragment.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadVideoFragment.kt
@@ -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
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/ActiveDownloadsFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/ActiveDownloadsFragment.kt
index 50bfec2b..4ea7e65d 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/ActiveDownloadsFragment.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/ActiveDownloadsFragment.kt
@@ -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
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/TerminalActivity.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/TerminalActivity.kt
index 7ca68dfb..ba35bbdf 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/TerminalActivity.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/TerminalActivity.kt
@@ -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() {
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/downloadLogs/DownloadLogFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/downloadLogs/DownloadLogFragment.kt
index 87b6c54a..a95e7bf0 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/downloadLogs/DownloadLogFragment.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/downloadLogs/DownloadLogFragment.kt
@@ -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
}
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/downloadLogs/DownloadLogListFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/downloadLogs/DownloadLogListFragment.kt
index 668b11a7..ab96bb28 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/downloadLogs/DownloadLogListFragment.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/downloadLogs/DownloadLogListFragment.kt
@@ -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()
+ }
}
}
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/BaseSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/BaseSettingsFragment.kt
index 7d7097cd..b6b372c7 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/BaseSettingsFragment.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/BaseSettingsFragment.kt
@@ -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)
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/DownloadSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/DownloadSettingsFragment.kt
index 9981eaa9..d00d8d58 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/DownloadSettingsFragment.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/DownloadSettingsFragment.kt
@@ -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())
+
+ val useScheduler = findPreference("use_scheduler")
+ val scheduleStart = findPreference("schedule_start")
+ scheduleStart?.summary = preferences.getString("schedule_start", "00:00")
+ val scheduleEnd = findPreference("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
+ }
+
+ 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
- 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
+ scheduler.schedule()
+ }
+ 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
+ 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
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/FolderSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/FolderSettingsFragment.kt
index 423d79ef..c829ee49 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/FolderSettingsFragment.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/FolderSettingsFragment.kt
@@ -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()
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/GeneralSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/GeneralSettingsFragment.kt
index 31ba4e4c..999a459c 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/GeneralSettingsFragment.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/GeneralSettingsFragment.kt
@@ -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("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
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/util/FileUtil.kt b/app/src/main/java/com/deniscerri/ytdlnis/util/FileUtil.kt
index 78a20f77..11701948 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/util/FileUtil.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/util/FileUtil.kt
@@ -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, context: Context) : List {
+
+ 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, context: Context) : List {
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 {
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt b/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt
index fb11ce81..10b75b7a 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt
@@ -133,13 +133,14 @@ class InfoUtil(private val context: Context) {
@Throws(JSONException::class)
fun getYoutubeVideo(url: String): List {
+ 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{
+ return try{
+ request.addOption("--print", "filename")
+ val res = YoutubeDL.getInstance().execute(request)
+ val results: Array = 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)
}
}
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/util/UiUtil.kt b/app/src/main/java/com/deniscerri/ytdlnis/util/UiUtil.kt
index 46de315a..afd4ffb2 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/util/UiUtil.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/util/UiUtil.kt
@@ -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(R.id.title).text = item.title
card.findViewById(R.id.content).text = item.content
- card.findViewById(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){
+ fun shareFileIntent(context: Context, paths: List){
val uris : ArrayList = 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,
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/work/AlarmScheduler.kt b/app/src/main/java/com/deniscerri/ytdlnis/work/AlarmScheduler.kt
new file mode 100644
index 00000000..f8bec179
--- /dev/null
+++ b/app/src/main/java/com/deniscerri/ytdlnis/work/AlarmScheduler.kt
@@ -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
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt b/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt
index 28127192..e5e3c080 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt
@@ -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?
- //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 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{
+ 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)
diff --git a/app/src/main/res/drawable/baseline_save_as_24.xml b/app/src/main/res/drawable/baseline_save_as_24.xml
new file mode 100644
index 00000000..dd11043d
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_save_as_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/layout/command_template_item.xml b/app/src/main/res/layout/command_template_item.xml
index 37b155b6..a5e578f9 100644
--- a/app/src/main/res/layout/command_template_item.xml
+++ b/app/src/main/res/layout/command_template_item.xml
@@ -19,13 +19,6 @@
android:layout_height="match_parent"
android:padding="15dp">
-
-
+ app:layout_constraintTop_toTopOf="@id/bottomsheet_download_button" />