add automatic backup to the app
This commit is contained in:
parent
8254640e5f
commit
1a597240fd
16 changed files with 779 additions and 533 deletions
|
|
@ -12,7 +12,7 @@ def properties = new Properties()
|
|||
def versionMajor = 1
|
||||
def versionMinor = 8
|
||||
def versionPatch = 2
|
||||
def versionBuild = 2 // bump for dogfood builds, public betas, etc.
|
||||
def versionBuild = 1 // bump for dogfood builds, public betas, etc.
|
||||
def isBeta = false
|
||||
|
||||
def versionExt = ""
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import com.deniscerri.ytdl.database.repository.DownloadRepository
|
|||
import com.deniscerri.ytdl.database.viewmodel.CookieViewModel
|
||||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
|
||||
import com.deniscerri.ytdl.database.viewmodel.SettingsViewModel
|
||||
import com.deniscerri.ytdl.ui.BaseActivity
|
||||
import com.deniscerri.ytdl.ui.HomeFragment
|
||||
import com.deniscerri.ytdl.ui.downloads.DownloadQueueMainFragment
|
||||
|
|
@ -46,6 +47,7 @@ import com.deniscerri.ytdl.util.CrashListener
|
|||
import com.deniscerri.ytdl.util.NavbarUtil
|
||||
import com.deniscerri.ytdl.util.NavbarUtil.applyNavBarStyle
|
||||
import com.deniscerri.ytdl.util.ThemeUtil
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.deniscerri.ytdl.util.UpdateUtil
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
|
@ -62,6 +64,7 @@ import kotlinx.coroutines.SupervisorJob
|
|||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
import java.io.Reader
|
||||
|
|
@ -77,6 +80,7 @@ class MainActivity : BaseActivity() {
|
|||
private lateinit var resultViewModel: ResultViewModel
|
||||
private lateinit var cookieViewModel: CookieViewModel
|
||||
private lateinit var downloadViewModel: DownloadViewModel
|
||||
private lateinit var settingsViewModel: SettingsViewModel
|
||||
private var navigationView: NavigationView? = null
|
||||
private var navigationBarView: NavigationBarView? = null
|
||||
private lateinit var navHostFragment : NavHostFragment
|
||||
|
|
@ -92,6 +96,7 @@ class MainActivity : BaseActivity() {
|
|||
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
||||
cookieViewModel = ViewModelProvider(this)[CookieViewModel::class.java]
|
||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||
settingsViewModel = ViewModelProvider(this)[SettingsViewModel::class.java]
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
||||
if (preferences.getBoolean("incognito", false)) {
|
||||
|
|
@ -479,8 +484,17 @@ class MainActivity : BaseActivity() {
|
|||
private fun checkUpdate() {
|
||||
if (preferences.getBoolean("update_app", false)) {
|
||||
val updateUtil = UpdateUtil(this)
|
||||
CoroutineScope(Dispatchers.IO).launch{
|
||||
updateUtil.updateApp{}
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val res = updateUtil.tryGetNewVersion()
|
||||
if (res.isSuccess) {
|
||||
if (preferences.getBoolean("automatic_backup", false)) {
|
||||
settingsViewModel.backup()
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
UiUtil.showNewAppUpdateDialog(res.getOrNull()!!, this@MainActivity, preferences)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,6 +175,10 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
return downloadDao.getErroredDownloadsList()
|
||||
}
|
||||
|
||||
fun getSavedDownloads() : List<DownloadItem> {
|
||||
return downloadDao.getSavedDownloadsList()
|
||||
}
|
||||
|
||||
fun getScheduledDownloadIDs() : List<Long> {
|
||||
return downloadDao.getScheduledDownloadIDs()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,22 @@
|
|||
package com.deniscerri.ytdl.database.repository
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.Data
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.database.dao.ObserveSourcesDao
|
||||
import com.deniscerri.ytdl.database.models.observeSources.ObserveSourcesItem
|
||||
import com.deniscerri.ytdl.work.ObserveSourceWorker
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import java.util.Calendar
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class ObserveSourcesRepository(private val observeSourcesDao: ObserveSourcesDao) {
|
||||
class ObserveSourcesRepository(private val observeSourcesDao: ObserveSourcesDao, private val workManager: WorkManager, private val sharedPreferences: SharedPreferences) {
|
||||
val items : Flow<List<ObserveSourcesItem>> = observeSourcesDao.getAllSourcesFlow()
|
||||
enum class SourceStatus {
|
||||
ACTIVE, STOPPED
|
||||
|
|
@ -58,4 +69,72 @@ class ObserveSourcesRepository(private val observeSourcesDao: ObserveSourcesDao)
|
|||
observeSourcesDao.update(item)
|
||||
}
|
||||
|
||||
fun cancelObservationTaskByID(id: Long){
|
||||
workManager.cancelAllWorkByTag("observation_$id")
|
||||
}
|
||||
|
||||
fun observeTask(it: ObserveSourcesItem){
|
||||
cancelObservationTaskByID(it.id)
|
||||
|
||||
Calendar.getInstance().apply {
|
||||
timeInMillis = it.startsTime
|
||||
|
||||
if (it.everyCategory != ObserveSourcesRepository.EveryCategory.HOUR){
|
||||
val hourMin = Calendar.getInstance()
|
||||
hourMin.timeInMillis = it.everyTime
|
||||
set(Calendar.HOUR_OF_DAY, hourMin.get(Calendar.HOUR_OF_DAY))
|
||||
set(Calendar.MINUTE, hourMin.get(Calendar.MINUTE))
|
||||
}
|
||||
|
||||
when(it.everyCategory){
|
||||
ObserveSourcesRepository.EveryCategory.HOUR -> {}
|
||||
ObserveSourcesRepository.EveryCategory.DAY -> {}
|
||||
ObserveSourcesRepository.EveryCategory.WEEK -> {
|
||||
var weekDayNr = get(Calendar.DAY_OF_WEEK) - 1
|
||||
if (weekDayNr == 0) weekDayNr = 7
|
||||
val followingWeekDay = it.weeklyConfig?.weekDays?.firstOrNull { it >= weekDayNr }
|
||||
if (followingWeekDay == null){
|
||||
add(
|
||||
Calendar.DAY_OF_MONTH,
|
||||
it.weeklyConfig?.weekDays?.minBy { it }?.plus((7 - weekDayNr)) ?: 0)
|
||||
}else{
|
||||
add(Calendar.DAY_OF_MONTH, followingWeekDay - weekDayNr)
|
||||
}
|
||||
}
|
||||
ObserveSourcesRepository.EveryCategory.MONTH -> {
|
||||
val currentMonthIndex = get(Calendar.MONTH)
|
||||
if (it.monthlyConfig?.startsMonth != currentMonthIndex){
|
||||
set(Calendar.MONTH, it.monthlyConfig?.startsMonth ?: 0)
|
||||
if (timeInMillis < Calendar.getInstance().timeInMillis){
|
||||
add(Calendar.YEAR, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//schedule for next time
|
||||
val allowMeteredNetworks = sharedPreferences.getBoolean("metered_networks", true)
|
||||
|
||||
val workConstraints = Constraints.Builder()
|
||||
if (!allowMeteredNetworks) workConstraints.setRequiredNetworkType(NetworkType.UNMETERED)
|
||||
else {
|
||||
workConstraints.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
}
|
||||
|
||||
val workRequest = OneTimeWorkRequestBuilder<ObserveSourceWorker>()
|
||||
.addTag("observeSources")
|
||||
.addTag("observation_${it.id}")
|
||||
.setConstraints(workConstraints.build())
|
||||
.setInitialDelay(timeInMillis - System.currentTimeMillis(), TimeUnit.MILLISECONDS)
|
||||
.setInputData(Data.Builder().putLong("id", it.id).build())
|
||||
|
||||
workManager.enqueueUniqueWork(
|
||||
"OBSERVE${it.id}",
|
||||
ExistingWorkPolicy.REPLACE,
|
||||
workRequest.build()
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -792,7 +792,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
}
|
||||
|
||||
fun getSaved() : List<DownloadItem> {
|
||||
return dao.getSavedDownloadsList()
|
||||
return repository.getSavedDownloads()
|
||||
}
|
||||
|
||||
fun getActiveDownloads() : List<DownloadItem>{
|
||||
|
|
|
|||
|
|
@ -30,10 +30,10 @@ class ObserveSourcesViewModel(private val application: Application) : AndroidVie
|
|||
|
||||
init {
|
||||
val dao = DBManager.getInstance(application).observeSourcesDao
|
||||
repository = ObserveSourcesRepository(dao)
|
||||
items = repository.items.asLiveData()
|
||||
workManager = WorkManager.getInstance(application)
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(application)
|
||||
repository = ObserveSourcesRepository(dao, workManager, preferences)
|
||||
items = repository.items.asLiveData()
|
||||
}
|
||||
|
||||
fun getAll(): List<ObserveSourcesItem> {
|
||||
|
|
@ -51,30 +51,30 @@ class ObserveSourcesViewModel(private val application: Application) : AndroidVie
|
|||
suspend fun insertUpdate(item: ObserveSourcesItem) : Long {
|
||||
if (item.id > 0) {
|
||||
repository.update(item)
|
||||
observeTask(item)
|
||||
repository.observeTask(item)
|
||||
return item.id
|
||||
}
|
||||
|
||||
val id = repository.insert(item)
|
||||
item.id = id
|
||||
if (id > 0) observeTask(item)
|
||||
if (id > 0) repository.observeTask(item)
|
||||
return id
|
||||
}
|
||||
|
||||
suspend fun stopObserving(item: ObserveSourcesItem) {
|
||||
item.status = ObserveSourcesRepository.SourceStatus.STOPPED
|
||||
repository.update(item)
|
||||
cancelObservationTaskByID(item.id)
|
||||
repository.cancelObservationTaskByID(item.id)
|
||||
}
|
||||
|
||||
fun delete(item: ObserveSourcesItem) = viewModelScope.launch(Dispatchers.IO) {
|
||||
runCatching { cancelObservationTaskByID(item.id) }
|
||||
runCatching { repository.cancelObservationTaskByID(item.id) }
|
||||
repository.delete(item)
|
||||
}
|
||||
|
||||
fun deleteAll() = viewModelScope.launch(Dispatchers.IO) {
|
||||
getAll().forEach {
|
||||
runCatching { cancelObservationTaskByID(it.id) }
|
||||
runCatching { repository.cancelObservationTaskByID(it.id) }
|
||||
}
|
||||
|
||||
repository.deleteAll()
|
||||
|
|
@ -83,76 +83,4 @@ class ObserveSourcesViewModel(private val application: Application) : AndroidVie
|
|||
suspend fun update(item: ObserveSourcesItem) {
|
||||
repository.update(item)
|
||||
}
|
||||
|
||||
private fun cancelObservationTaskByID(id: Long){
|
||||
workManager.cancelAllWorkByTag("observation_$id")
|
||||
}
|
||||
|
||||
|
||||
|
||||
private fun observeTask(it: ObserveSourcesItem){
|
||||
cancelObservationTaskByID(it.id)
|
||||
|
||||
Calendar.getInstance().apply {
|
||||
timeInMillis = it.startsTime
|
||||
|
||||
if (it.everyCategory != ObserveSourcesRepository.EveryCategory.HOUR){
|
||||
val hourMin = Calendar.getInstance()
|
||||
hourMin.timeInMillis = it.everyTime
|
||||
set(Calendar.HOUR_OF_DAY, hourMin.get(Calendar.HOUR_OF_DAY))
|
||||
set(Calendar.MINUTE, hourMin.get(Calendar.MINUTE))
|
||||
}
|
||||
|
||||
when(it.everyCategory){
|
||||
ObserveSourcesRepository.EveryCategory.HOUR -> {}
|
||||
ObserveSourcesRepository.EveryCategory.DAY -> {}
|
||||
ObserveSourcesRepository.EveryCategory.WEEK -> {
|
||||
var weekDayNr = get(Calendar.DAY_OF_WEEK) - 1
|
||||
if (weekDayNr == 0) weekDayNr = 7
|
||||
val followingWeekDay = it.weeklyConfig?.weekDays?.firstOrNull { it >= weekDayNr }
|
||||
if (followingWeekDay == null){
|
||||
add(Calendar.DAY_OF_MONTH,
|
||||
it.weeklyConfig?.weekDays?.minBy { it }?.plus((7 - weekDayNr)) ?: 0)
|
||||
}else{
|
||||
add(Calendar.DAY_OF_MONTH, followingWeekDay - weekDayNr)
|
||||
}
|
||||
}
|
||||
ObserveSourcesRepository.EveryCategory.MONTH -> {
|
||||
val currentMonthIndex = get(Calendar.MONTH)
|
||||
if (it.monthlyConfig?.startsMonth != currentMonthIndex){
|
||||
set(Calendar.MONTH, it.monthlyConfig?.startsMonth ?: 0)
|
||||
if (timeInMillis < Calendar.getInstance().timeInMillis){
|
||||
add(Calendar.YEAR, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//schedule for next time
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
|
||||
val allowMeteredNetworks = sharedPreferences.getBoolean("metered_networks", true)
|
||||
|
||||
val workConstraints = Constraints.Builder()
|
||||
if (!allowMeteredNetworks) workConstraints.setRequiredNetworkType(NetworkType.UNMETERED)
|
||||
else {
|
||||
workConstraints.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
}
|
||||
|
||||
val workRequest = OneTimeWorkRequestBuilder<ObserveSourceWorker>()
|
||||
.addTag("observeSources")
|
||||
.addTag("observation_${it.id}")
|
||||
.setConstraints(workConstraints.build())
|
||||
.setInitialDelay(timeInMillis - System.currentTimeMillis(), TimeUnit.MILLISECONDS)
|
||||
.setInputData(Data.Builder().putLong("id", it.id).build())
|
||||
|
||||
workManager.enqueueUniqueWork(
|
||||
"OBSERVE${it.id}",
|
||||
ExistingWorkPolicy.REPLACE,
|
||||
workRequest.build()
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,235 @@
|
|||
package com.deniscerri.ytdl.database.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
import androidx.core.content.edit
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.PeriodicWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import com.deniscerri.ytdl.BuildConfig
|
||||
import com.deniscerri.ytdl.database.DBManager
|
||||
import com.deniscerri.ytdl.database.models.RestoreAppDataItem
|
||||
import com.deniscerri.ytdl.database.repository.CommandTemplateRepository
|
||||
import com.deniscerri.ytdl.database.repository.CookieRepository
|
||||
import com.deniscerri.ytdl.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdl.database.repository.HistoryRepository
|
||||
import com.deniscerri.ytdl.database.repository.ObserveSourcesRepository
|
||||
import com.deniscerri.ytdl.database.repository.SearchHistoryRepository
|
||||
import com.deniscerri.ytdl.util.BackupSettingsUtil
|
||||
import com.deniscerri.ytdl.util.FileUtil
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonObject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.util.Calendar
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
class SettingsViewModel(private val application: Application) : AndroidViewModel(application) {
|
||||
private val workManager : WorkManager = WorkManager.getInstance(application)
|
||||
private val preferences : SharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
|
||||
|
||||
private val historyRepository : HistoryRepository
|
||||
private val downloadRepository : DownloadRepository
|
||||
private val cookieRepository : CookieRepository
|
||||
private val commandTemplateRepository : CommandTemplateRepository
|
||||
private val searchHistoryRepository : SearchHistoryRepository
|
||||
private val observeSourcesRepository : ObserveSourcesRepository
|
||||
|
||||
init {
|
||||
val dbManager = DBManager.getInstance(application)
|
||||
historyRepository = HistoryRepository(dbManager.historyDao)
|
||||
downloadRepository = DownloadRepository(dbManager.downloadDao)
|
||||
cookieRepository = CookieRepository(dbManager.cookieDao)
|
||||
commandTemplateRepository = CommandTemplateRepository(dbManager.commandTemplateDao)
|
||||
searchHistoryRepository = SearchHistoryRepository(dbManager.searchHistoryDao)
|
||||
observeSourcesRepository = ObserveSourcesRepository(dbManager.observeSourcesDao, workManager, preferences)
|
||||
}
|
||||
|
||||
suspend fun backup(items: List<String> = listOf()) : Result<String> {
|
||||
var list = items
|
||||
if (list.isEmpty()) {
|
||||
list = listOf("settings", "downloads", "queued", "scheduled", "cancelled", "errored", "saved", "cookies", "templates", "shortcuts", "searchHistory", "observeSources")
|
||||
}
|
||||
|
||||
val json = JsonObject()
|
||||
json.addProperty("app", "YTDLnis_backup")
|
||||
list.forEach {
|
||||
runCatching {
|
||||
when(it){
|
||||
"settings" -> json.add("settings", BackupSettingsUtil.backupSettings(preferences))
|
||||
"downloads" -> json.add("downloads", BackupSettingsUtil.backupHistory(historyRepository))
|
||||
"queued" -> json.add("queued", BackupSettingsUtil.backupQueuedDownloads(downloadRepository))
|
||||
"scheduled" -> json.add("scheduled", BackupSettingsUtil.backupScheduledDownloads(downloadRepository))
|
||||
"cancelled" -> json.add("cancelled", BackupSettingsUtil.backupCancelledDownloads(downloadRepository))
|
||||
"errored" -> json.add("errored", BackupSettingsUtil.backupErroredDownloads(downloadRepository))
|
||||
"saved" -> json.add("saved", BackupSettingsUtil.backupSavedDownloads(downloadRepository))
|
||||
"cookies" -> json.add("cookies", BackupSettingsUtil.backupCookies(cookieRepository))
|
||||
"templates" -> json.add("templates", BackupSettingsUtil.backupCommandTemplates(commandTemplateRepository))
|
||||
"shortcuts" -> json.add("shortcuts", BackupSettingsUtil.backupShortcuts(commandTemplateRepository))
|
||||
"searchHistory" -> json.add("search_history", BackupSettingsUtil.backupSearchHistory(searchHistoryRepository))
|
||||
"observeSources" -> json.add("observe_sources", BackupSettingsUtil.backupObserveSources(observeSourcesRepository))
|
||||
}
|
||||
}.onFailure {err ->
|
||||
return Result.failure(err)
|
||||
}
|
||||
}
|
||||
|
||||
val currentTime = Calendar.getInstance()
|
||||
val dir = File(FileUtil.getCachePath(application) + "/Backups")
|
||||
dir.mkdirs()
|
||||
|
||||
val saveFile = File("${dir.absolutePath}/YTDLnis_Backup_${BuildConfig.VERSION_NAME}_${currentTime.get(
|
||||
Calendar.YEAR)}-${currentTime.get(Calendar.MONTH) + 1}-${currentTime.get(
|
||||
Calendar.DAY_OF_MONTH)}.json")
|
||||
|
||||
saveFile.delete()
|
||||
withContext(Dispatchers.IO) {
|
||||
saveFile.createNewFile()
|
||||
}
|
||||
saveFile.writeText(GsonBuilder().setPrettyPrinting().create().toJson(json))
|
||||
|
||||
val res = withContext(Dispatchers.IO) {
|
||||
FileUtil.moveFile(saveFile.parentFile!!, application, FileUtil.getBackupPath(application), false) {}
|
||||
}
|
||||
|
||||
return Result.success(res[0])
|
||||
}
|
||||
|
||||
suspend fun restoreData(data: RestoreAppDataItem, context: Context, resetData: Boolean = false) : Boolean {
|
||||
val result = kotlin.runCatching {
|
||||
data.settings?.apply {
|
||||
val prefs = this
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit(commit = true){
|
||||
clear()
|
||||
prefs.forEach {
|
||||
val key : String = it.asJsonObject.get("key").toString().replace("\"", "")
|
||||
when(it.asJsonObject.get("type").toString().replace("\"", "")){
|
||||
"String" -> {
|
||||
val value = it.asJsonObject.get("value").toString().replace("\"", "")
|
||||
putString(key, value)
|
||||
}
|
||||
"Boolean" -> {
|
||||
val value = it.asJsonObject.get("value").toString().replace("\"", "").toBoolean()
|
||||
Log.e("REST", value.toString())
|
||||
Log.e("REST", key)
|
||||
putBoolean(key, value)
|
||||
}
|
||||
"Int" -> {
|
||||
val value = it.asJsonObject.get("value").toString().replace("\"", "").toInt()
|
||||
putInt(key, value)
|
||||
}
|
||||
"HashSet" -> {
|
||||
val value = it.asJsonObject.get("value").toString().replace("(\")|(\\[)|(])|([ \\t])".toRegex(), "").split(",")
|
||||
putStringSet(key, value.toHashSet())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data.downloads?.apply {
|
||||
withContext(Dispatchers.IO){
|
||||
if (resetData) historyRepository.deleteAll(false)
|
||||
data.downloads!!.forEach {
|
||||
historyRepository.insert(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.queued?.apply {
|
||||
withContext(Dispatchers.IO){
|
||||
if (resetData) downloadRepository.deleteQueued()
|
||||
data.queued!!.forEach {
|
||||
downloadRepository.insert(it)
|
||||
}
|
||||
downloadRepository.startDownloadWorker(listOf(), application)
|
||||
}
|
||||
}
|
||||
|
||||
data.cancelled?.apply {
|
||||
withContext(Dispatchers.IO){
|
||||
if (resetData) downloadRepository.deleteCancelled()
|
||||
data.cancelled!!.forEach {
|
||||
downloadRepository.insert(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.errored?.apply {
|
||||
withContext(Dispatchers.IO){
|
||||
if (resetData) downloadRepository.deleteErrored()
|
||||
data.errored!!.forEach {
|
||||
downloadRepository.insert(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.saved?.apply {
|
||||
withContext(Dispatchers.IO){
|
||||
if (resetData) downloadRepository.deleteSaved()
|
||||
data.saved!!.forEach {
|
||||
downloadRepository.insert(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.cookies?.apply {
|
||||
withContext(Dispatchers.IO){
|
||||
if (resetData) cookieRepository.deleteAll()
|
||||
data.cookies!!.forEach {
|
||||
cookieRepository.insert(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.templates?.apply {
|
||||
withContext(Dispatchers.IO){
|
||||
if (resetData) commandTemplateRepository.deleteAll()
|
||||
data.templates!!.forEach {
|
||||
commandTemplateRepository.insert(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.shortcuts?.apply {
|
||||
withContext(Dispatchers.IO){
|
||||
if (resetData) commandTemplateRepository.deleteAllShortcuts()
|
||||
data.shortcuts!!.forEach {
|
||||
commandTemplateRepository.insertShortcut(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.searchHistory?.apply {
|
||||
withContext(Dispatchers.IO){
|
||||
if (resetData) searchHistoryRepository.deleteAll()
|
||||
data.searchHistory!!.forEach {
|
||||
searchHistoryRepository.insert(it.query)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.observeSources?.apply {
|
||||
withContext(Dispatchers.IO){
|
||||
if (resetData) observeSourcesRepository.deleteAll()
|
||||
data.observeSources!!.forEach {
|
||||
observeSourcesRepository.insert(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result.isSuccess
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -25,6 +25,7 @@ import androidx.navigation.fragment.findNavController
|
|||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import com.deniscerri.ytdl.BuildConfig
|
||||
|
|
@ -44,6 +45,8 @@ import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
|||
import com.deniscerri.ytdl.database.viewmodel.HistoryViewModel
|
||||
import com.deniscerri.ytdl.database.viewmodel.ObserveSourcesViewModel
|
||||
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
|
||||
import com.deniscerri.ytdl.database.viewmodel.SettingsViewModel
|
||||
import com.deniscerri.ytdl.ui.more.settings.FolderSettingsFragment.Companion.COMMAND_PATH_CODE
|
||||
import com.deniscerri.ytdl.util.FileUtil
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.deniscerri.ytdl.util.UpdateUtil
|
||||
|
|
@ -67,22 +70,22 @@ import java.util.Locale
|
|||
class MainSettingsFragment : PreferenceFragmentCompat() {
|
||||
private var backup : Preference? = null
|
||||
private var restore : Preference? = null
|
||||
private var backupPath : Preference? = null
|
||||
|
||||
private var updateUtil: UpdateUtil? = null
|
||||
private var activeDownloadCount = 0
|
||||
|
||||
private lateinit var resultViewModel: ResultViewModel
|
||||
private lateinit var historyViewModel: HistoryViewModel
|
||||
private lateinit var downloadViewModel: DownloadViewModel
|
||||
private lateinit var cookieViewModel: CookieViewModel
|
||||
private lateinit var commandTemplateViewModel: CommandTemplateViewModel
|
||||
private lateinit var observeSourcesViewModel: ObserveSourcesViewModel
|
||||
private lateinit var settingsViewModel: SettingsViewModel
|
||||
|
||||
private lateinit var editor: SharedPreferences.Editor
|
||||
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.root_preferences, rootKey)
|
||||
val navController = findNavController()
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
editor = preferences.edit()
|
||||
|
||||
val appearance = findPreference<Preference>("appearance")
|
||||
val separator = if (Locale(preferences.getString("app_language", "en")!!).layoutDirection == LayoutDirection.RTL) "،" else ","
|
||||
appearance?.summary = "${if (Build.VERSION.SDK_INT < 33) getString(R.string.language) + "$separator " else ""}${getString(R.string.Theme)}$separator ${getString(R.string.accents)}$separator ${getString(R.string.preferred_search_engine)}"
|
||||
|
|
@ -135,16 +138,11 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
|||
if (w.state == WorkInfo.State.RUNNING) activeDownloadCount++
|
||||
}
|
||||
}
|
||||
|
||||
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
||||
historyViewModel = ViewModelProvider(this)[HistoryViewModel::class.java]
|
||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||
cookieViewModel = ViewModelProvider(this)[CookieViewModel::class.java]
|
||||
commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java]
|
||||
observeSourcesViewModel = ViewModelProvider(this)[ObserveSourcesViewModel::class.java]
|
||||
settingsViewModel = ViewModelProvider(this)[SettingsViewModel::class.java]
|
||||
|
||||
backup = findPreference("backup")
|
||||
restore = findPreference("restore")
|
||||
backupPath = findPreference("backup_path")
|
||||
|
||||
findPreference<Preference>("package_name")?.apply {
|
||||
summary = BuildConfig.APPLICATION_ID
|
||||
|
|
@ -175,68 +173,38 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
|||
builder.setPositiveButton(
|
||||
getString(R.string.ok)
|
||||
) { _: DialogInterface?, _: Int ->
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
lifecycleScope.launch {
|
||||
if (checkedItems.all { !it }){
|
||||
withContext(Dispatchers.Main){
|
||||
Snackbar.make(requireView(), R.string.select_backup_categories, Snackbar.LENGTH_SHORT).show()
|
||||
}
|
||||
Snackbar.make(requireView(), R.string.select_backup_categories, Snackbar.LENGTH_SHORT).show()
|
||||
return@launch
|
||||
}
|
||||
val json = JsonObject()
|
||||
json.addProperty("app", "YTDLnis_backup")
|
||||
for (i in 0 until checkedItems.size) {
|
||||
if (checkedItems[i]) {
|
||||
runCatching {
|
||||
when(values[i]){
|
||||
"settings" -> json.add("settings", backupSettings(preferences))
|
||||
"downloads" -> json.add("downloads", backupHistory())
|
||||
"queued" -> json.add("queued", backupQueuedDownloads() )
|
||||
"scheduled" -> json.add("scheduled", backupScheduledDownloads() )
|
||||
"cancelled" -> json.add("cancelled", backupCancelledDownloads() )
|
||||
"errored" -> json.add("errored", backupErroredDownloads() )
|
||||
"saved" -> json.add("saved", backupSavedDownloads() )
|
||||
"cookies" -> json.add("cookies", backupCookies() )
|
||||
"templates" -> json.add("templates", backupCommandTemplates() )
|
||||
"shortcuts" -> json.add("shortcuts", backupShortcuts() )
|
||||
"searchHistory" -> json.add("search_history", backupSearchHistory() )
|
||||
"observeSources" -> json.add("observe_sources", backupObserveSources() )
|
||||
}
|
||||
}.onFailure {err ->
|
||||
withContext(Dispatchers.Main){
|
||||
val snack = Snackbar.make(requireView(), err.message ?: requireContext().getString(R.string.errored), Snackbar.LENGTH_LONG)
|
||||
val snackbarView: View = snack.view
|
||||
val snackTextView = snackbarView.findViewById<View>(com.google.android.material.R.id.snackbar_text) as TextView
|
||||
snackTextView.maxLines = 9999999
|
||||
snack.setAction(android.R.string.copy){
|
||||
UiUtil.copyToClipboard(err.message ?: requireContext().getString(R.string.errored), requireActivity())
|
||||
}
|
||||
snack.show()
|
||||
}
|
||||
}
|
||||
|
||||
val selectedItems = values.mapIndexed { idx, it -> Pair<String, Boolean>(it, checkedItems[idx]) }.filter { it.second }.map { it.first }
|
||||
val pathResult = withContext(Dispatchers.IO){
|
||||
settingsViewModel.backup(selectedItems)
|
||||
}
|
||||
|
||||
if (pathResult.isFailure) {
|
||||
val errorMessage = pathResult.exceptionOrNull()?.message ?: requireContext().getString(R.string.errored)
|
||||
val snack = Snackbar.make(requireView(), errorMessage, Snackbar.LENGTH_LONG)
|
||||
val snackbarView: View = snack.view
|
||||
val snackTextView = snackbarView.findViewById<View>(com.google.android.material.R.id.snackbar_text) as TextView
|
||||
snackTextView.maxLines = 9999999
|
||||
snack.setAction(android.R.string.copy){
|
||||
UiUtil.copyToClipboard(errorMessage ?: requireContext().getString(R.string.errored), requireActivity())
|
||||
}
|
||||
snack.show()
|
||||
}else {
|
||||
val s = Snackbar.make(
|
||||
requireView(),
|
||||
getString(R.string.backup_created_successfully),
|
||||
Snackbar.LENGTH_LONG
|
||||
)
|
||||
s.setAction(R.string.Open_File) {
|
||||
FileUtil.openFileIntent(requireActivity(), pathResult.getOrNull()!!)
|
||||
}
|
||||
s.show()
|
||||
}
|
||||
|
||||
val currentTime = Calendar.getInstance()
|
||||
val dir = File("${FileUtil.getCachePath(requireContext())}/Backups")
|
||||
dir.mkdirs()
|
||||
|
||||
val saveFile = File("${dir.absolutePath}/YTDLnis_Backup_${BuildConfig.VERSION_NAME}_${currentTime.get(
|
||||
Calendar.YEAR)}-${currentTime.get(Calendar.MONTH) + 1}-${currentTime.get(
|
||||
Calendar.DAY_OF_MONTH)}_${currentTime.get(Calendar.HOUR_OF_DAY)}-${currentTime.get(Calendar.MINUTE)}-${currentTime.get(Calendar.SECOND)}.json")
|
||||
|
||||
saveFile.delete()
|
||||
saveFile.createNewFile()
|
||||
saveFile.writeText(GsonBuilder().setPrettyPrinting().create().toJson(json))
|
||||
|
||||
val res = withContext(Dispatchers.IO) {
|
||||
FileUtil.moveFile(saveFile.parentFile!!, requireContext(), FileUtil.getDefaultApplicationPath() + "/Backups", false) {}
|
||||
}
|
||||
|
||||
val s = Snackbar.make(requireView(), getString(R.string.backup_created_successfully), Snackbar.LENGTH_LONG)
|
||||
s.setAction(R.string.Open_File){
|
||||
FileUtil.openFileIntent(requireActivity(), res[0])
|
||||
}
|
||||
s.show()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -260,179 +228,39 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
|||
true
|
||||
}
|
||||
|
||||
backupPath?.apply {
|
||||
summary = FileUtil.formatPath(FileUtil.getBackupPath(requireContext()))
|
||||
setOnPreferenceClickListener {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
||||
backupPathResultLauncher.launch(intent)
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun backupSettings(preferences: SharedPreferences) : JsonArray {
|
||||
runCatching {
|
||||
val prefs = preferences.all
|
||||
prefs.remove("app_language")
|
||||
val arr = JsonArray()
|
||||
prefs.forEach {
|
||||
val obj = JsonObject()
|
||||
obj.addProperty("key", it.key)
|
||||
obj.addProperty("value", it.value.toString())
|
||||
obj.addProperty("type", it.value!!::class.simpleName)
|
||||
arr.add(obj)
|
||||
private var backupPathResultLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
result.data?.data?.let {
|
||||
activity?.contentResolver?.takePersistableUriPermission(
|
||||
it,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
}
|
||||
return arr
|
||||
|
||||
val path = result.data!!.data.toString()
|
||||
backupPath!!.summary = FileUtil.formatPath(path)
|
||||
editor.putString("backup_path", path)
|
||||
editor.apply()
|
||||
}
|
||||
return JsonArray()
|
||||
}
|
||||
|
||||
private suspend fun backupHistory() : JsonArray {
|
||||
runCatching {
|
||||
val historyItems = withContext(Dispatchers.IO) {
|
||||
historyViewModel.getAll()
|
||||
}
|
||||
val arr = JsonArray()
|
||||
historyItems.forEach {
|
||||
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
return JsonArray()
|
||||
}
|
||||
|
||||
private suspend fun backupQueuedDownloads() : JsonArray {
|
||||
runCatching {
|
||||
val items = withContext(Dispatchers.IO) {
|
||||
downloadViewModel.getQueued()
|
||||
}
|
||||
val arr = JsonArray()
|
||||
items.forEach {
|
||||
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
return JsonArray()
|
||||
}
|
||||
|
||||
private suspend fun backupScheduledDownloads() : JsonArray {
|
||||
runCatching {
|
||||
val items = withContext(Dispatchers.IO) {
|
||||
downloadViewModel.getScheduled()
|
||||
}
|
||||
val arr = JsonArray()
|
||||
items.forEach {
|
||||
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
return JsonArray()
|
||||
}
|
||||
|
||||
private suspend fun backupCancelledDownloads() : JsonArray {
|
||||
runCatching {
|
||||
val items = withContext(Dispatchers.IO) {
|
||||
downloadViewModel.getCancelled()
|
||||
}
|
||||
val arr = JsonArray()
|
||||
items.forEach {
|
||||
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
return JsonArray()
|
||||
}
|
||||
|
||||
private suspend fun backupErroredDownloads() : JsonArray {
|
||||
runCatching {
|
||||
val items = withContext(Dispatchers.IO) {
|
||||
downloadViewModel.getErrored()
|
||||
}
|
||||
val arr = JsonArray()
|
||||
items.forEach {
|
||||
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
return JsonArray()
|
||||
}
|
||||
|
||||
private suspend fun backupSavedDownloads() : JsonArray {
|
||||
runCatching {
|
||||
val items = withContext(Dispatchers.IO) {
|
||||
downloadViewModel.getSaved()
|
||||
}
|
||||
val arr = JsonArray()
|
||||
items.forEach {
|
||||
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
return JsonArray()
|
||||
}
|
||||
|
||||
private suspend fun backupCookies() : JsonArray {
|
||||
runCatching {
|
||||
val items = withContext(Dispatchers.IO) {
|
||||
cookieViewModel.getAll()
|
||||
}
|
||||
val arr = JsonArray()
|
||||
items.forEach {
|
||||
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
return JsonArray()
|
||||
}
|
||||
|
||||
private suspend fun backupCommandTemplates() : JsonArray {
|
||||
runCatching {
|
||||
val items = withContext(Dispatchers.IO) {
|
||||
commandTemplateViewModel.getAll()
|
||||
}
|
||||
val arr = JsonArray()
|
||||
items.forEach {
|
||||
it.useAsExtraCommand = false
|
||||
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
return JsonArray()
|
||||
}
|
||||
|
||||
private suspend fun backupShortcuts() : JsonArray {
|
||||
runCatching {
|
||||
val items = withContext(Dispatchers.IO) {
|
||||
commandTemplateViewModel.getAllShortcuts()
|
||||
}
|
||||
val arr = JsonArray()
|
||||
items.forEach {
|
||||
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
return JsonArray()
|
||||
}
|
||||
|
||||
private suspend fun backupSearchHistory() : JsonArray {
|
||||
runCatching {
|
||||
val historyItems = withContext(Dispatchers.IO) {
|
||||
resultViewModel.getSearchHistory()
|
||||
}
|
||||
val arr = JsonArray()
|
||||
historyItems.forEach {
|
||||
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
return JsonArray()
|
||||
}
|
||||
|
||||
private suspend fun backupObserveSources() : JsonArray {
|
||||
runCatching {
|
||||
val observeSourcesItems = withContext(Dispatchers.IO) {
|
||||
observeSourcesViewModel.getAll()
|
||||
}
|
||||
val arr = JsonArray()
|
||||
observeSourcesItems.forEach {
|
||||
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
return JsonArray()
|
||||
}
|
||||
|
||||
private var appRestoreResultLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
|
|
@ -590,10 +418,28 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
|||
|
||||
showAppRestoreInfoDialog(
|
||||
onMerge = {
|
||||
restoreData(restoreData,parsedDataMessage.toString())
|
||||
lifecycleScope.launch {
|
||||
val res = withContext(Dispatchers.IO){
|
||||
settingsViewModel.restoreData(restoreData, requireContext())
|
||||
}
|
||||
if (res) {
|
||||
showRestoreFinishedDialog(restoreData, parsedDataMessage.toString())
|
||||
}else{
|
||||
throw Error()
|
||||
}
|
||||
}
|
||||
},
|
||||
onReset = {
|
||||
restoreData(restoreData,parsedDataMessage.toString(), true)
|
||||
lifecycleScope.launch {
|
||||
val res = withContext(Dispatchers.IO){
|
||||
settingsViewModel.restoreData(restoreData, requireContext(),true)
|
||||
}
|
||||
if (res) {
|
||||
showRestoreFinishedDialog(restoreData, parsedDataMessage.toString())
|
||||
}else{
|
||||
throw Error()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -634,128 +480,7 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
|||
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).gravity = Gravity.START
|
||||
}
|
||||
|
||||
private fun restoreData(data: RestoreAppDataItem, restoreDataMessage: String, resetData: Boolean = false) = lifecycleScope.launch {
|
||||
data.settings?.apply {
|
||||
val prefs = this
|
||||
PreferenceManager.getDefaultSharedPreferences(requireContext()).edit(commit = true){
|
||||
clear()
|
||||
prefs.forEach {
|
||||
val key : String = it.asJsonObject.get("key").toString().replace("\"", "")
|
||||
when(it.asJsonObject.get("type").toString().replace("\"", "")){
|
||||
"String" -> {
|
||||
val value = it.asJsonObject.get("value").toString().replace("\"", "")
|
||||
putString(key, value)
|
||||
}
|
||||
"Boolean" -> {
|
||||
val value = it.asJsonObject.get("value").toString().replace("\"", "").toBoolean()
|
||||
Log.e("REST", value.toString())
|
||||
Log.e("REST", key)
|
||||
putBoolean(key, value)
|
||||
}
|
||||
"Int" -> {
|
||||
val value = it.asJsonObject.get("value").toString().replace("\"", "").toInt()
|
||||
putInt(key, value)
|
||||
}
|
||||
"HashSet" -> {
|
||||
val value = it.asJsonObject.get("value").toString().replace("(\")|(\\[)|(])|([ \\t])".toRegex(), "").split(",")
|
||||
putStringSet(key, value.toHashSet())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data.downloads?.apply {
|
||||
withContext(Dispatchers.IO){
|
||||
if (resetData) historyViewModel.deleteAll(false)
|
||||
data.downloads!!.forEach {
|
||||
historyViewModel.insert(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.queued?.apply {
|
||||
withContext(Dispatchers.IO){
|
||||
if (resetData) downloadViewModel.deleteQueued()
|
||||
data.queued!!.forEach {
|
||||
downloadViewModel.insert(it)
|
||||
}
|
||||
downloadViewModel.queueDownloads(listOf())
|
||||
}
|
||||
}
|
||||
|
||||
data.cancelled?.apply {
|
||||
withContext(Dispatchers.IO){
|
||||
if (resetData) downloadViewModel.deleteCancelled()
|
||||
data.cancelled!!.forEach {
|
||||
downloadViewModel.insert(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.errored?.apply {
|
||||
withContext(Dispatchers.IO){
|
||||
if (resetData) downloadViewModel.deleteErrored()
|
||||
data.errored!!.forEach {
|
||||
downloadViewModel.insert(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.saved?.apply {
|
||||
withContext(Dispatchers.IO){
|
||||
if (resetData) downloadViewModel.deleteSaved()
|
||||
data.saved!!.forEach {
|
||||
downloadViewModel.insert(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.cookies?.apply {
|
||||
withContext(Dispatchers.IO){
|
||||
if (resetData) cookieViewModel.deleteAll()
|
||||
data.cookies!!.forEach {
|
||||
cookieViewModel.insert(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.templates?.apply {
|
||||
withContext(Dispatchers.IO){
|
||||
if (resetData) commandTemplateViewModel.deleteAll()
|
||||
data.templates!!.forEach {
|
||||
commandTemplateViewModel.insert(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.shortcuts?.apply {
|
||||
withContext(Dispatchers.IO){
|
||||
if (resetData) commandTemplateViewModel.deleteAllShortcuts()
|
||||
data.shortcuts!!.forEach {
|
||||
commandTemplateViewModel.insertShortcut(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.searchHistory?.apply {
|
||||
withContext(Dispatchers.IO){
|
||||
if (resetData) resultViewModel.deleteAllSearchQueryHistory()
|
||||
data.searchHistory!!.forEach {
|
||||
resultViewModel.addSearchQueryToHistory(it.query)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.observeSources?.apply {
|
||||
withContext(Dispatchers.IO){
|
||||
if (resetData) observeSourcesViewModel.deleteAll()
|
||||
data.observeSources!!.forEach {
|
||||
observeSourcesViewModel.insertUpdate(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun showRestoreFinishedDialog(data: RestoreAppDataItem, restoreDataMessage: String) {
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
|
||||
val builder = MaterialAlertDialogBuilder(requireContext())
|
||||
|
|
|
|||
|
|
@ -1,16 +1,24 @@
|
|||
package com.deniscerri.ytdl.ui.more.settings
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.DownloadManager
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
|
|
@ -24,9 +32,11 @@ import android.widget.LinearLayout
|
|||
import android.widget.ListView
|
||||
import android.widget.PopupMenu
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.get
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
|
|
@ -38,7 +48,9 @@ import com.deniscerri.ytdl.BuildConfig
|
|||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.database.models.CommandTemplate
|
||||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdl.database.viewmodel.SettingsViewModel
|
||||
import com.deniscerri.ytdl.database.viewmodel.YTDLPViewModel
|
||||
import com.deniscerri.ytdl.util.FileUtil
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.deniscerri.ytdl.util.UiUtil.showShortcutsSheet
|
||||
import com.deniscerri.ytdl.util.UpdateUtil
|
||||
|
|
@ -52,10 +64,14 @@ import com.google.android.material.materialswitch.MaterialSwitch
|
|||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.yausername.youtubedl_android.YoutubeDL
|
||||
import io.noties.markwon.AbstractMarkwonPlugin
|
||||
import io.noties.markwon.Markwon
|
||||
import io.noties.markwon.MarkwonConfiguration
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
|
|
@ -67,8 +83,9 @@ class UpdateSettingsFragment : BaseSettingsFragment() {
|
|||
private var updateUtil: UpdateUtil? = null
|
||||
private var version: Preference? = null
|
||||
private lateinit var preferences: SharedPreferences
|
||||
private lateinit var ytdlpViewModel: YTDLPViewModel
|
||||
|
||||
private lateinit var ytdlpViewModel: YTDLPViewModel
|
||||
private lateinit var settingsViewModel: SettingsViewModel
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.updating_preferences, rootKey)
|
||||
|
|
@ -79,6 +96,7 @@ class UpdateSettingsFragment : BaseSettingsFragment() {
|
|||
ytdlSource = findPreference("ytdlp_source_label")
|
||||
|
||||
ytdlpViewModel = ViewModelProvider(this)[YTDLPViewModel::class.java]
|
||||
settingsViewModel = ViewModelProvider(this)[SettingsViewModel::class.java]
|
||||
|
||||
ytdlSource?.apply {
|
||||
summary = preferences.getString("ytdlp_source_label", "")!!.ifEmpty { getString(R.string.update_ytdl_stable) }
|
||||
|
|
@ -125,16 +143,19 @@ class UpdateSettingsFragment : BaseSettingsFragment() {
|
|||
version!!.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
lifecycleScope.launch{
|
||||
withContext(Dispatchers.IO){
|
||||
updateUtil!!.updateApp{ msg ->
|
||||
lifecycleScope.launch(Dispatchers.Main){
|
||||
view?.apply {
|
||||
Snackbar.make(requireView(), msg, Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
val res = withContext(Dispatchers.IO){
|
||||
updateUtil!!.tryGetNewVersion()
|
||||
}
|
||||
if (res.isFailure) {
|
||||
Snackbar.make(requireView(), res.exceptionOrNull()?.message ?: getString(R.string.network_error), Snackbar.LENGTH_LONG).show()
|
||||
}else{
|
||||
if (preferences.getBoolean("automatic_backup", false)) {
|
||||
withContext(Dispatchers.IO){
|
||||
settingsViewModel.backup()
|
||||
}
|
||||
}
|
||||
UiUtil.showNewAppUpdateDialog(res.getOrNull()!!, requireActivity(), preferences)
|
||||
}
|
||||
|
||||
}
|
||||
true
|
||||
}
|
||||
|
|
|
|||
190
app/src/main/java/com/deniscerri/ytdl/util/BackupSettingsUtil.kt
Normal file
190
app/src/main/java/com/deniscerri/ytdl/util/BackupSettingsUtil.kt
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
package com.deniscerri.ytdl.util
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import com.deniscerri.ytdl.database.repository.CommandTemplateRepository
|
||||
import com.deniscerri.ytdl.database.repository.CookieRepository
|
||||
import com.deniscerri.ytdl.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdl.database.repository.HistoryRepository
|
||||
import com.deniscerri.ytdl.database.repository.ObserveSourcesRepository
|
||||
import com.deniscerri.ytdl.database.repository.SearchHistoryRepository
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonParser
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
object BackupSettingsUtil {
|
||||
fun backupSettings(preferences: SharedPreferences) : JsonArray {
|
||||
runCatching {
|
||||
val prefs = preferences.all
|
||||
prefs.remove("app_language")
|
||||
val arr = JsonArray()
|
||||
prefs.forEach {
|
||||
val obj = JsonObject()
|
||||
obj.addProperty("key", it.key)
|
||||
obj.addProperty("value", it.value.toString())
|
||||
obj.addProperty("type", it.value!!::class.simpleName)
|
||||
arr.add(obj)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
return JsonArray()
|
||||
}
|
||||
|
||||
suspend fun backupHistory(historyRepository: HistoryRepository) : JsonArray {
|
||||
runCatching {
|
||||
val historyItems = withContext(Dispatchers.IO) {
|
||||
historyRepository.getAll()
|
||||
}
|
||||
val arr = JsonArray()
|
||||
historyItems.forEach {
|
||||
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
return JsonArray()
|
||||
}
|
||||
|
||||
suspend fun backupQueuedDownloads(downloadRepository: DownloadRepository) : JsonArray {
|
||||
runCatching {
|
||||
val items = withContext(Dispatchers.IO) {
|
||||
downloadRepository.getQueuedDownloads()
|
||||
}
|
||||
val arr = JsonArray()
|
||||
items.forEach {
|
||||
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
return JsonArray()
|
||||
}
|
||||
|
||||
suspend fun backupScheduledDownloads(downloadRepository: DownloadRepository) : JsonArray {
|
||||
runCatching {
|
||||
val items = withContext(Dispatchers.IO) {
|
||||
downloadRepository.getScheduledDownloads()
|
||||
}
|
||||
val arr = JsonArray()
|
||||
items.forEach {
|
||||
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
return JsonArray()
|
||||
}
|
||||
|
||||
suspend fun backupCancelledDownloads(downloadRepository: DownloadRepository) : JsonArray {
|
||||
runCatching {
|
||||
val items = withContext(Dispatchers.IO) {
|
||||
downloadRepository.getCancelledDownloads()
|
||||
}
|
||||
val arr = JsonArray()
|
||||
items.forEach {
|
||||
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
return JsonArray()
|
||||
}
|
||||
|
||||
suspend fun backupErroredDownloads(downloadRepository: DownloadRepository) : JsonArray {
|
||||
runCatching {
|
||||
val items = withContext(Dispatchers.IO) {
|
||||
downloadRepository.getErroredDownloads()
|
||||
}
|
||||
val arr = JsonArray()
|
||||
items.forEach {
|
||||
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
return JsonArray()
|
||||
}
|
||||
|
||||
suspend fun backupSavedDownloads(downloadRepository: DownloadRepository) : JsonArray {
|
||||
runCatching {
|
||||
val items = withContext(Dispatchers.IO) {
|
||||
downloadRepository.getSavedDownloads()
|
||||
}
|
||||
val arr = JsonArray()
|
||||
items.forEach {
|
||||
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
return JsonArray()
|
||||
}
|
||||
|
||||
suspend fun backupCookies(cookieRepository: CookieRepository) : JsonArray {
|
||||
runCatching {
|
||||
val items = withContext(Dispatchers.IO) {
|
||||
cookieRepository.getAll()
|
||||
}
|
||||
val arr = JsonArray()
|
||||
items.forEach {
|
||||
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
return JsonArray()
|
||||
}
|
||||
|
||||
suspend fun backupCommandTemplates(commandTemplateRepository: CommandTemplateRepository) : JsonArray {
|
||||
runCatching {
|
||||
val items = withContext(Dispatchers.IO) {
|
||||
commandTemplateRepository.getAll()
|
||||
}
|
||||
val arr = JsonArray()
|
||||
items.forEach {
|
||||
it.useAsExtraCommand = false
|
||||
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
return JsonArray()
|
||||
}
|
||||
|
||||
suspend fun backupShortcuts(commandTemplateRepository: CommandTemplateRepository) : JsonArray {
|
||||
runCatching {
|
||||
val items = withContext(Dispatchers.IO) {
|
||||
commandTemplateRepository.getAllShortCuts()
|
||||
}
|
||||
val arr = JsonArray()
|
||||
items.forEach {
|
||||
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
return JsonArray()
|
||||
}
|
||||
|
||||
suspend fun backupSearchHistory(searchHistoryRepository: SearchHistoryRepository) : JsonArray {
|
||||
runCatching {
|
||||
val historyItems = withContext(Dispatchers.IO) {
|
||||
searchHistoryRepository.getAll()
|
||||
}
|
||||
val arr = JsonArray()
|
||||
historyItems.forEach {
|
||||
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
return JsonArray()
|
||||
}
|
||||
|
||||
suspend fun backupObserveSources(observeSourcesRepository: ObserveSourcesRepository) : JsonArray {
|
||||
runCatching {
|
||||
val observeSourcesItems = withContext(Dispatchers.IO) {
|
||||
observeSourcesRepository.getAll()
|
||||
}
|
||||
val arr = JsonArray()
|
||||
observeSourcesItems.forEach {
|
||||
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
return JsonArray()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -284,9 +284,18 @@ object FileUtil {
|
|||
return listOf()
|
||||
}
|
||||
|
||||
fun getBackupPath(context: Context) : String {
|
||||
val preference = PreferenceManager.getDefaultSharedPreferences(context).getString("backup_path", "")
|
||||
return if (preference.isNullOrBlank() || !File(formatPath(preference)).canWrite()) {
|
||||
getDefaultApplicationPath() + "/Backups"
|
||||
}else {
|
||||
formatPath(preference)
|
||||
}
|
||||
}
|
||||
|
||||
fun getCachePath(context: Context) : String {
|
||||
val preference = PreferenceManager.getDefaultSharedPreferences(context).getString("cache_path", "")!!
|
||||
if (preference.isEmpty() || !File(formatPath(preference)).canWrite()) {
|
||||
val preference = PreferenceManager.getDefaultSharedPreferences(context).getString("cache_path", "")
|
||||
if (preference.isNullOrBlank() || !File(formatPath(preference)).canWrite()) {
|
||||
val externalPath = context.getExternalFilesDir(null)
|
||||
return if (externalPath == null){
|
||||
context.cacheDir.absolutePath + "/downloads/"
|
||||
|
|
|
|||
|
|
@ -5,17 +5,22 @@ import android.animation.ObjectAnimator
|
|||
import android.animation.ValueAnimator
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.DownloadManager
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.text.format.DateFormat
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
|
|
@ -51,6 +56,7 @@ import com.deniscerri.ytdl.R
|
|||
import com.deniscerri.ytdl.database.models.CommandTemplate
|
||||
import com.deniscerri.ytdl.database.models.DownloadItem
|
||||
import com.deniscerri.ytdl.database.models.Format
|
||||
import com.deniscerri.ytdl.database.models.GithubRelease
|
||||
import com.deniscerri.ytdl.database.models.HistoryItem
|
||||
import com.deniscerri.ytdl.database.models.TemplateShortcut
|
||||
import com.deniscerri.ytdl.database.models.YoutubePlayerClientItem
|
||||
|
|
@ -86,6 +92,9 @@ import com.google.android.material.textfield.TextInputLayout.END_ICON_NONE
|
|||
import com.google.android.material.timepicker.MaterialTimePicker
|
||||
import com.google.android.material.timepicker.TimeFormat
|
||||
import com.google.gson.Gson
|
||||
import io.noties.markwon.AbstractMarkwonPlugin
|
||||
import io.noties.markwon.Markwon
|
||||
import io.noties.markwon.MarkwonConfiguration
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -2494,4 +2503,86 @@ object UiUtil {
|
|||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
}
|
||||
|
||||
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
||||
fun showNewAppUpdateDialog(v: GithubRelease, context: Activity, preferences: SharedPreferences) {
|
||||
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
val skippedVersions = preferences.getString("skip_updates", "")?.split(",")?.distinct()?.toMutableList() ?: mutableListOf()
|
||||
|
||||
val updateDialog = MaterialAlertDialogBuilder(context)
|
||||
.setTitle(v.tag_name)
|
||||
.setMessage(v.body)
|
||||
.setIcon(R.drawable.ic_update_app)
|
||||
.setNeutralButton(R.string.ignore){ d: DialogInterface?, _:Int ->
|
||||
skippedVersions.add(v.tag_name)
|
||||
preferences.edit().putString("skip_updates", skippedVersions.joinToString(",")).apply()
|
||||
d?.dismiss()
|
||||
}
|
||||
.setNegativeButton(context.getString(R.string.cancel)) { _: DialogInterface?, _: Int -> }
|
||||
.setPositiveButton(context.getString(R.string.update)) { d: DialogInterface?, _: Int ->
|
||||
runCatching {
|
||||
val releaseVersion = v.assets.firstOrNull { it.name.contains(Build.SUPPORTED_ABIS[0]) }
|
||||
if (releaseVersion == null){
|
||||
Toast.makeText(context, R.string.couldnt_find_apk, Toast.LENGTH_SHORT).show()
|
||||
return@runCatching
|
||||
}
|
||||
|
||||
|
||||
val uri = Uri.parse(releaseVersion.browser_download_url)
|
||||
Environment
|
||||
.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
||||
.mkdirs()
|
||||
val downloadID = downloadManager.enqueue(
|
||||
DownloadManager.Request(uri)
|
||||
.setAllowedNetworkTypes(
|
||||
DownloadManager.Request.NETWORK_WIFI or
|
||||
DownloadManager.Request.NETWORK_MOBILE
|
||||
)
|
||||
.setAllowedOverRoaming(true)
|
||||
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
||||
.setTitle(releaseVersion.name)
|
||||
.setDescription(context.getString(R.string.downloading_update))
|
||||
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, releaseVersion.name)
|
||||
)
|
||||
|
||||
val onDownloadComplete: BroadcastReceiver =
|
||||
object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent) {
|
||||
context?.unregisterReceiver(this)
|
||||
FileUtil.openFileIntent(context!!,
|
||||
Environment.getExternalStoragePublicDirectory(
|
||||
Environment.DIRECTORY_DOWNLOADS)?.absolutePath +
|
||||
File.separator + releaseVersion.name)
|
||||
}
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
if (Build.VERSION.SDK_INT >= 33) {
|
||||
context.registerReceiver(onDownloadComplete, IntentFilter(
|
||||
DownloadManager.ACTION_DOWNLOAD_COMPLETE),
|
||||
Context.RECEIVER_NOT_EXPORTED
|
||||
)
|
||||
}else{
|
||||
context.registerReceiver(onDownloadComplete, IntentFilter(
|
||||
DownloadManager.ACTION_DOWNLOAD_COMPLETE)
|
||||
)
|
||||
}
|
||||
}
|
||||
d?.dismiss()
|
||||
}
|
||||
}
|
||||
val view = updateDialog.show()
|
||||
val textView = view.findViewById<TextView>(android.R.id.message)
|
||||
textView!!.movementMethod = LinkMovementMethod.getInstance()
|
||||
val mw = Markwon.builder(context).usePlugin(object: AbstractMarkwonPlugin() {
|
||||
|
||||
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
|
||||
builder.linkResolver { view, link ->
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
|
||||
context.startActivity(browserIntent)
|
||||
}
|
||||
}
|
||||
}).build()
|
||||
mw.setMarkdown(textView, v.body)
|
||||
}
|
||||
}
|
||||
|
|
@ -47,7 +47,6 @@ import java.net.URL
|
|||
class UpdateUtil(var context: Context) {
|
||||
private val tag = "UpdateUtil"
|
||||
private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
private val downloadManager: DownloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
|
||||
private val channelMap = mapOf(
|
||||
Pair<String, YoutubeDL.UpdateChannel>("stable", YoutubeDL.UpdateChannel.STABLE),
|
||||
|
|
@ -56,21 +55,20 @@ class UpdateUtil(var context: Context) {
|
|||
)
|
||||
|
||||
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
||||
fun updateApp(result: (result: String) -> Unit) {
|
||||
fun tryGetNewVersion() : Result<GithubRelease> {
|
||||
try {
|
||||
val skippedVersions = sharedPreferences.getString("skip_updates", "")?.split(",")?.distinct()?.toMutableList() ?: mutableListOf()
|
||||
val res = getGithubReleases()
|
||||
|
||||
if (res.isEmpty()){
|
||||
result(context.getString(R.string.network_error))
|
||||
return
|
||||
return Result.failure(Error(context.getString(R.string.network_error)))
|
||||
}
|
||||
|
||||
val useBeta = sharedPreferences.getBoolean("update_beta", false)
|
||||
var v: GithubRelease?
|
||||
val v: GithubRelease
|
||||
if (useBeta){
|
||||
v = res.firstOrNull { it.tag_name.contains("beta", true) && res.indexOf(it) == 0 }
|
||||
if (v == null) v = res.first()
|
||||
val tmp = res.firstOrNull { it.tag_name.contains("beta", true) && res.indexOf(it) == 0 }
|
||||
v = tmp ?: res.first()
|
||||
}else{
|
||||
v = res.first { !it.tag_name.contains("beta", true) }
|
||||
}
|
||||
|
|
@ -88,80 +86,13 @@ class UpdateUtil(var context: Context) {
|
|||
if (skippedVersions.contains(v.tag_name)) isInLatest = true
|
||||
|
||||
if (isInLatest){
|
||||
result(context.getString(R.string.you_are_in_latest_version))
|
||||
return
|
||||
return Result.failure(Error(context.getString(R.string.you_are_in_latest_version)))
|
||||
}
|
||||
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
val updateDialog = MaterialAlertDialogBuilder(context)
|
||||
.setTitle(v.tag_name)
|
||||
.setMessage(v.body)
|
||||
.setIcon(R.drawable.ic_update_app)
|
||||
.setNeutralButton(R.string.ignore){ d: DialogInterface?, _:Int ->
|
||||
skippedVersions.add(v.tag_name)
|
||||
sharedPreferences.edit().putString("skip_updates", skippedVersions.joinToString(",")).apply()
|
||||
d?.dismiss()
|
||||
}
|
||||
.setNegativeButton(context.resources.getString(R.string.cancel)) { _: DialogInterface?, _: Int -> }
|
||||
.setPositiveButton(context.resources.getString(R.string.update)) { d: DialogInterface?, _: Int ->
|
||||
runCatching {
|
||||
val releaseVersion = v.assets.firstOrNull { it.name.contains(Build.SUPPORTED_ABIS[0]) }
|
||||
if (releaseVersion == null){
|
||||
Toast.makeText(context, R.string.couldnt_find_apk, Toast.LENGTH_SHORT).show()
|
||||
return@runCatching
|
||||
}
|
||||
|
||||
|
||||
val uri = Uri.parse(releaseVersion.browser_download_url)
|
||||
Environment
|
||||
.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
||||
.mkdirs()
|
||||
val downloadID = downloadManager.enqueue(
|
||||
DownloadManager.Request(uri)
|
||||
.setAllowedNetworkTypes(
|
||||
DownloadManager.Request.NETWORK_WIFI or
|
||||
DownloadManager.Request.NETWORK_MOBILE
|
||||
)
|
||||
.setAllowedOverRoaming(true)
|
||||
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
||||
.setTitle(releaseVersion.name)
|
||||
.setDescription(context.getString(R.string.downloading_update))
|
||||
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, releaseVersion.name)
|
||||
)
|
||||
|
||||
val onDownloadComplete: BroadcastReceiver =
|
||||
object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent) {
|
||||
context?.unregisterReceiver(this)
|
||||
FileUtil.openFileIntent(context!!,
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)?.absolutePath +
|
||||
File.separator + releaseVersion.name)
|
||||
}
|
||||
}
|
||||
|
||||
context.registerReceiver(onDownloadComplete, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
|
||||
d?.dismiss()
|
||||
}
|
||||
}
|
||||
val view = updateDialog.show()
|
||||
val textView = view.findViewById<TextView>(android.R.id.message)
|
||||
textView!!.movementMethod = LinkMovementMethod.getInstance()
|
||||
val mw = Markwon.builder(context).usePlugin(object: AbstractMarkwonPlugin() {
|
||||
|
||||
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
|
||||
builder.linkResolver { view, link ->
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
|
||||
startActivity(context, browserIntent, Bundle())
|
||||
}
|
||||
}
|
||||
}).build()
|
||||
mw.setMarkdown(textView, v.body)
|
||||
}
|
||||
return
|
||||
return Result.success(v)
|
||||
}catch (e: Exception){
|
||||
e.printStackTrace()
|
||||
result(e.message.toString())
|
||||
return
|
||||
return Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,13 +40,14 @@ class ObserveSourceWorker(
|
|||
val sourceID = inputData.getLong("id", 0)
|
||||
if (sourceID == 0L) return Result.failure()
|
||||
val dbManager = DBManager.getInstance(context)
|
||||
val repo = ObserveSourcesRepository(dbManager.observeSourcesDao)
|
||||
val workManager = WorkManager.getInstance(context)
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
val repo = ObserveSourcesRepository(dbManager.observeSourcesDao, workManager, sharedPreferences)
|
||||
val historyRepo = HistoryRepository(dbManager.historyDao)
|
||||
val downloadRepo = DownloadRepository(dbManager.downloadDao)
|
||||
val commandTemplateDao = dbManager.commandTemplateDao
|
||||
val ytdlpUtil = YTDLPUtil(context, commandTemplateDao)
|
||||
val resultRepository = ResultRepository(dbManager.resultDao, commandTemplateDao, context)
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
||||
val item = repo.getByID(sourceID)
|
||||
if (item.status == ObserveSourcesRepository.SourceStatus.STOPPED){
|
||||
|
|
|
|||
|
|
@ -467,4 +467,7 @@
|
|||
<string name="use_format_sorting">Use Format Importance Ordering</string>
|
||||
<string name="use_code_highlighter">Use Code Color Highlighter</string>
|
||||
<string name="reset_preferences_in_screen">Reset all preferences in this screen</string>
|
||||
<string name="automatic_backup">Automatic backup</string>
|
||||
<string name="backup_path">Backup Path</string>
|
||||
<string name="automatic_backup_summary">Automatically make a backup of everything when a new version the application is found</string>
|
||||
</resources>
|
||||
|
|
@ -41,6 +41,21 @@
|
|||
app:icon="@drawable/baseline_restore_page_24"
|
||||
app:key="restore"
|
||||
app:title="@string/restore"/>
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="false"
|
||||
app:icon="@drawable/ic_clock"
|
||||
app:key="automatic_backup"
|
||||
android:summary="@string/automatic_backup_summary"
|
||||
app:title="@string/automatic_backup" />
|
||||
|
||||
<Preference
|
||||
app:icon="@drawable/baseline_folder_24"
|
||||
android:summary=""
|
||||
app:key="backup_path"
|
||||
app:title="@string/backup_path" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/about" >
|
||||
|
|
|
|||
Loading…
Reference in a new issue