mirror of
https://github.com/libre-tube/LibreTube.git
synced 2024-12-14 22:30:30 +05:30
Merge pull request #2970 from Isira-Seneviratne/Backup_restore_improvements
Make backup and restore improvements.
This commit is contained in:
commit
3d78250daf
@ -4,6 +4,8 @@ import android.content.Context
|
|||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
fun Context.toastFromMainThread(text: String) {
|
fun Context.toastFromMainThread(text: String) {
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
@ -18,3 +20,11 @@ fun Context.toastFromMainThread(text: String) {
|
|||||||
fun Context.toastFromMainThread(stringId: Int) {
|
fun Context.toastFromMainThread(stringId: Int) {
|
||||||
toastFromMainThread(getString(stringId))
|
toastFromMainThread(getString(stringId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun Context.toastFromMainDispatcher(text: String) = withContext(Dispatchers.Main) {
|
||||||
|
Toast.makeText(this@toastFromMainDispatcher, text, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun Context.toastFromMainDispatcher(stringId: Int) {
|
||||||
|
toastFromMainDispatcher(getString(stringId))
|
||||||
|
}
|
||||||
|
@ -11,8 +11,6 @@ import com.github.libretube.db.DatabaseHolder.Companion.Database
|
|||||||
import com.github.libretube.extensions.TAG
|
import com.github.libretube.extensions.TAG
|
||||||
import com.github.libretube.obj.BackupFile
|
import com.github.libretube.obj.BackupFile
|
||||||
import com.github.libretube.obj.PreferenceItem
|
import com.github.libretube.obj.PreferenceItem
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.json.booleanOrNull
|
import kotlinx.serialization.json.booleanOrNull
|
||||||
import kotlinx.serialization.json.decodeFromStream
|
import kotlinx.serialization.json.decodeFromStream
|
||||||
@ -24,35 +22,30 @@ import kotlinx.serialization.json.longOrNull
|
|||||||
/**
|
/**
|
||||||
* Backup and restore the preferences
|
* Backup and restore the preferences
|
||||||
*/
|
*/
|
||||||
class BackupHelper(private val context: Context) {
|
object BackupHelper {
|
||||||
/**
|
/**
|
||||||
* Write a [BackupFile] containing the database content as well as the preferences
|
* Write a [BackupFile] containing the database content as well as the preferences
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalSerializationApi::class)
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
fun createAdvancedBackup(uri: Uri?, backupFile: BackupFile) {
|
fun createAdvancedBackup(context: Context, uri: Uri, backupFile: BackupFile) {
|
||||||
uri?.let {
|
|
||||||
try {
|
try {
|
||||||
context.contentResolver.openOutputStream(it)?.use { outputStream ->
|
context.contentResolver.openOutputStream(uri)?.use { outputStream ->
|
||||||
JsonHelper.json.encodeToStream(backupFile, outputStream)
|
JsonHelper.json.encodeToStream(backupFile, outputStream)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG(), "Error while writing backup: $e")
|
Log.e(TAG(), "Error while writing backup: $e")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restore data from a [BackupFile]
|
* Restore data from a [BackupFile]
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalSerializationApi::class)
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
fun restoreAdvancedBackup(uri: Uri?) {
|
suspend fun restoreAdvancedBackup(context: Context, uri: Uri) {
|
||||||
val backupFile = uri?.let {
|
val backupFile = context.contentResolver.openInputStream(uri)?.use {
|
||||||
context.contentResolver.openInputStream(it)?.use { inputStream ->
|
JsonHelper.json.decodeFromStream<BackupFile>(it)
|
||||||
JsonHelper.json.decodeFromStream<BackupFile>(inputStream)
|
|
||||||
}
|
|
||||||
} ?: return
|
} ?: return
|
||||||
|
|
||||||
runBlocking(Dispatchers.IO) {
|
|
||||||
Database.watchHistoryDao().insertAll(
|
Database.watchHistoryDao().insertAll(
|
||||||
*backupFile.watchHistory.orEmpty().toTypedArray()
|
*backupFile.watchHistory.orEmpty().toTypedArray()
|
||||||
)
|
)
|
||||||
@ -79,14 +72,13 @@ class BackupHelper(private val context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
restorePreferences(backupFile.preferences)
|
restorePreferences(context, backupFile.preferences)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restore the shared preferences from a backup file
|
* Restore the shared preferences from a backup file
|
||||||
*/
|
*/
|
||||||
private fun restorePreferences(preferences: List<PreferenceItem>?) {
|
private fun restorePreferences(context: Context, preferences: List<PreferenceItem>?) {
|
||||||
if (preferences == null) return
|
if (preferences == null) return
|
||||||
PreferenceManager.getDefaultSharedPreferences(context).edit(commit = true) {
|
PreferenceManager.getDefaultSharedPreferences(context).edit(commit = true) {
|
||||||
// clear the previous settings
|
// clear the previous settings
|
||||||
|
@ -3,7 +3,6 @@ package com.github.libretube.helpers
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.api.JsonHelper
|
import com.github.libretube.api.JsonHelper
|
||||||
import com.github.libretube.api.PlaylistsHelper
|
import com.github.libretube.api.PlaylistsHelper
|
||||||
@ -11,52 +10,42 @@ import com.github.libretube.api.RetrofitInstance
|
|||||||
import com.github.libretube.api.SubscriptionHelper
|
import com.github.libretube.api.SubscriptionHelper
|
||||||
import com.github.libretube.db.DatabaseHolder.Companion.Database
|
import com.github.libretube.db.DatabaseHolder.Companion.Database
|
||||||
import com.github.libretube.extensions.TAG
|
import com.github.libretube.extensions.TAG
|
||||||
import com.github.libretube.extensions.toastFromMainThread
|
import com.github.libretube.extensions.toastFromMainDispatcher
|
||||||
import com.github.libretube.obj.ImportPlaylist
|
import com.github.libretube.obj.ImportPlaylist
|
||||||
import com.github.libretube.obj.ImportPlaylistFile
|
import com.github.libretube.obj.ImportPlaylistFile
|
||||||
import com.github.libretube.obj.NewPipeSubscription
|
import com.github.libretube.obj.NewPipeSubscription
|
||||||
import com.github.libretube.obj.NewPipeSubscriptions
|
import com.github.libretube.obj.NewPipeSubscriptions
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.json.decodeFromStream
|
import kotlinx.serialization.json.decodeFromStream
|
||||||
import kotlinx.serialization.json.encodeToStream
|
import kotlinx.serialization.json.encodeToStream
|
||||||
import okio.use
|
import okio.use
|
||||||
|
|
||||||
class ImportHelper(
|
object ImportHelper {
|
||||||
private val activity: Activity
|
|
||||||
) {
|
|
||||||
/**
|
/**
|
||||||
* Import subscriptions by a file uri
|
* Import subscriptions by a file uri
|
||||||
*/
|
*/
|
||||||
fun importSubscriptions(uri: Uri?) {
|
suspend fun importSubscriptions(activity: Activity, uri: Uri) {
|
||||||
if (uri == null) return
|
|
||||||
try {
|
try {
|
||||||
val applicationContext = activity.applicationContext
|
SubscriptionHelper.importSubscriptions(getChannelsFromUri(activity, uri))
|
||||||
val channels = getChannelsFromUri(uri)
|
activity.toastFromMainDispatcher(R.string.importsuccess)
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
SubscriptionHelper.importSubscriptions(channels)
|
|
||||||
}.invokeOnCompletion {
|
|
||||||
applicationContext.toastFromMainThread(R.string.importsuccess)
|
|
||||||
}
|
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
Log.e(TAG(), e.toString())
|
Log.e(TAG(), e.toString())
|
||||||
activity.toastFromMainThread(
|
activity.toastFromMainDispatcher(
|
||||||
activity.getString(R.string.unsupported_file_format) +
|
activity.getString(R.string.unsupported_file_format) +
|
||||||
" (${activity.contentResolver.getType(uri)})"
|
" (${activity.contentResolver.getType(uri)})"
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG(), e.toString())
|
Log.e(TAG(), e.toString())
|
||||||
Toast.makeText(activity, e.localizedMessage, Toast.LENGTH_SHORT).show()
|
e.localizedMessage?.let {
|
||||||
|
activity.toastFromMainDispatcher(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a list of channel IDs from a file [Uri]
|
* Get a list of channel IDs from a file [Uri]
|
||||||
*/
|
*/
|
||||||
private fun getChannelsFromUri(uri: Uri): List<String> {
|
private fun getChannelsFromUri(activity: Activity, uri: Uri): List<String> {
|
||||||
return when (val fileType = activity.contentResolver.getType(uri)) {
|
return when (val fileType = activity.contentResolver.getType(uri)) {
|
||||||
"application/json", "application/*", "application/octet-stream" -> {
|
"application/json", "application/*", "application/octet-stream" -> {
|
||||||
// NewPipe subscriptions format
|
// NewPipe subscriptions format
|
||||||
@ -84,10 +73,7 @@ class ImportHelper(
|
|||||||
/**
|
/**
|
||||||
* Write the text to the document
|
* Write the text to the document
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalSerializationApi::class)
|
suspend fun exportSubscriptions(activity: Activity, uri: Uri) {
|
||||||
fun exportSubscriptions(uri: Uri?) {
|
|
||||||
if (uri == null) return
|
|
||||||
runBlocking(Dispatchers.IO) {
|
|
||||||
val token = PreferenceHelper.getToken()
|
val token = PreferenceHelper.getToken()
|
||||||
val subs = if (token.isNotEmpty()) {
|
val subs = if (token.isNotEmpty()) {
|
||||||
RetrofitInstance.authApi.subscriptions(token)
|
RetrofitInstance.authApi.subscriptions(token)
|
||||||
@ -104,17 +90,14 @@ class ImportHelper(
|
|||||||
JsonHelper.json.encodeToStream(newPipeSubscriptions, it)
|
JsonHelper.json.encodeToStream(newPipeSubscriptions, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
activity.toastFromMainThread(R.string.exportsuccess)
|
activity.toastFromMainDispatcher(R.string.exportsuccess)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Import Playlists
|
* Import Playlists
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalSerializationApi::class)
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
fun importPlaylists(uri: Uri?) {
|
suspend fun importPlaylists(activity: Activity, uri: Uri) {
|
||||||
if (uri == null) return
|
|
||||||
|
|
||||||
val importPlaylists = mutableListOf<ImportPlaylist>()
|
val importPlaylists = mutableListOf<ImportPlaylist>()
|
||||||
|
|
||||||
when (val fileType = activity.contentResolver.getType(uri)) {
|
when (val fileType = activity.contentResolver.getType(uri)) {
|
||||||
@ -139,7 +122,7 @@ class ImportHelper(
|
|||||||
importPlaylists.addAll(playlistFile?.playlists.orEmpty())
|
importPlaylists.addAll(playlistFile?.playlists.orEmpty())
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
activity.applicationContext.toastFromMainThread("Unsupported file type $fileType")
|
activity.toastFromMainDispatcher("Unsupported file type $fileType")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -148,15 +131,13 @@ class ImportHelper(
|
|||||||
importPlaylists.forEach { playlist ->
|
importPlaylists.forEach { playlist ->
|
||||||
playlist.videos = playlist.videos.map { it.takeLast(11) }
|
playlist.videos = playlist.videos.map { it.takeLast(11) }
|
||||||
}
|
}
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
try {
|
try {
|
||||||
PlaylistsHelper.importPlaylists(importPlaylists)
|
PlaylistsHelper.importPlaylists(importPlaylists)
|
||||||
activity.applicationContext.toastFromMainThread(R.string.success)
|
activity.toastFromMainDispatcher(R.string.success)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG(), e.toString())
|
Log.e(TAG(), e.toString())
|
||||||
e.localizedMessage?.let {
|
e.localizedMessage?.let {
|
||||||
activity.applicationContext.toastFromMainThread(it)
|
activity.toastFromMainDispatcher(it)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -164,10 +145,7 @@ class ImportHelper(
|
|||||||
/**
|
/**
|
||||||
* Export Playlists
|
* Export Playlists
|
||||||
*/
|
*/
|
||||||
fun exportPlaylists(uri: Uri?) {
|
suspend fun exportPlaylists(activity: Activity, uri: Uri) {
|
||||||
if (uri == null) return
|
|
||||||
|
|
||||||
runBlocking {
|
|
||||||
val playlists = PlaylistsHelper.exportPlaylists()
|
val playlists = PlaylistsHelper.exportPlaylists()
|
||||||
val playlistFile = ImportPlaylistFile("Piped", 1, playlists)
|
val playlistFile = ImportPlaylistFile("Piped", 1, playlists)
|
||||||
|
|
||||||
@ -175,7 +153,6 @@ class ImportHelper(
|
|||||||
JsonHelper.json.encodeToStream(playlistFile, it)
|
JsonHelper.json.encodeToStream(playlistFile, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
activity.toastFromMainThread(R.string.exportsuccess)
|
activity.toastFromMainDispatcher(R.string.exportsuccess)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
package com.github.libretube.ui.preferences
|
package com.github.libretube.ui.preferences
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
|
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.helpers.BackupHelper
|
import com.github.libretube.helpers.BackupHelper
|
||||||
@ -12,68 +11,69 @@ import com.github.libretube.helpers.ImportHelper
|
|||||||
import com.github.libretube.obj.BackupFile
|
import com.github.libretube.obj.BackupFile
|
||||||
import com.github.libretube.ui.base.BasePreferenceFragment
|
import com.github.libretube.ui.base.BasePreferenceFragment
|
||||||
import com.github.libretube.ui.dialogs.BackupDialog
|
import com.github.libretube.ui.dialogs.BackupDialog
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
class BackupRestoreSettings : BasePreferenceFragment() {
|
class BackupRestoreSettings : BasePreferenceFragment() {
|
||||||
private val backupDateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd-HH:mm:ss")
|
private val backupDateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd-HH:mm:ss")
|
||||||
|
private var backupFile = BackupFile()
|
||||||
|
|
||||||
override val titleResourceId: Int = R.string.backup_restore
|
override val titleResourceId: Int = R.string.backup_restore
|
||||||
|
|
||||||
// backup and restore database
|
// backup and restore database
|
||||||
private lateinit var getBackupFile: ActivityResultLauncher<String>
|
private val getBackupFile = registerForActivityResult(ActivityResultContracts.GetContent()) {
|
||||||
private lateinit var createBackupFile: ActivityResultLauncher<String>
|
it?.let {
|
||||||
private var backupFile = BackupFile()
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
BackupHelper.restoreAdvancedBackup(requireContext(), it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private val createBackupFile = registerForActivityResult(CreateDocument(JSON)) {
|
||||||
|
it?.let {
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
BackupHelper.createAdvancedBackup(requireContext(), it, backupFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* result listeners for importing and exporting subscriptions
|
* result listeners for importing and exporting subscriptions
|
||||||
*/
|
*/
|
||||||
private lateinit var getSubscriptionsFile: ActivityResultLauncher<String>
|
private val getSubscriptionsFile = registerForActivityResult(
|
||||||
private lateinit var createSubscriptionsFile: ActivityResultLauncher<String>
|
ActivityResultContracts.GetContent()
|
||||||
|
) {
|
||||||
|
it?.let {
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
ImportHelper.importSubscriptions(requireActivity(), it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private val createSubscriptionsFile = registerForActivityResult(CreateDocument(JSON)) {
|
||||||
|
it?.let {
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
ImportHelper.exportSubscriptions(requireActivity(), it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* result listeners for importing and exporting playlists
|
* result listeners for importing and exporting playlists
|
||||||
*/
|
*/
|
||||||
private lateinit var getPlaylistsFile: ActivityResultLauncher<String>
|
private val getPlaylistsFile = registerForActivityResult(ActivityResultContracts.GetContent()) {
|
||||||
private lateinit var createPlaylistsFile: ActivityResultLauncher<String>
|
it?.let {
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
ImportHelper.importPlaylists(requireActivity(), it)
|
||||||
getSubscriptionsFile =
|
|
||||||
registerForActivityResult(
|
|
||||||
ActivityResultContracts.GetContent()
|
|
||||||
) { uri ->
|
|
||||||
ImportHelper(requireActivity()).importSubscriptions(uri)
|
|
||||||
}
|
}
|
||||||
createSubscriptionsFile = registerForActivityResult(
|
|
||||||
CreateDocument("application/json")
|
|
||||||
) { uri ->
|
|
||||||
ImportHelper(requireActivity()).exportSubscriptions(uri)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getPlaylistsFile = registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
|
|
||||||
ImportHelper(requireActivity()).importPlaylists(uri)
|
|
||||||
}
|
}
|
||||||
|
private val createPlaylistsFile = registerForActivityResult(CreateDocument(JSON)) {
|
||||||
createPlaylistsFile = registerForActivityResult(
|
it?.let {
|
||||||
CreateDocument("application/json")
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
) { uri ->
|
ImportHelper.exportPlaylists(requireActivity(), it)
|
||||||
ImportHelper(requireActivity()).exportPlaylists(uri)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getBackupFile =
|
|
||||||
registerForActivityResult(
|
|
||||||
ActivityResultContracts.GetContent()
|
|
||||||
) { uri: Uri? ->
|
|
||||||
BackupHelper(requireContext()).restoreAdvancedBackup(uri)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createBackupFile = registerForActivityResult(
|
|
||||||
CreateDocument("application/json")
|
|
||||||
) { uri: Uri? ->
|
|
||||||
BackupHelper(requireContext()).createAdvancedBackup(uri, backupFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
@ -116,8 +116,12 @@ class BackupRestoreSettings : BasePreferenceFragment() {
|
|||||||
|
|
||||||
val restoreAdvancedBackup = findPreference<Preference>("restore")
|
val restoreAdvancedBackup = findPreference<Preference>("restore")
|
||||||
restoreAdvancedBackup?.setOnPreferenceClickListener {
|
restoreAdvancedBackup?.setOnPreferenceClickListener {
|
||||||
getBackupFile.launch("application/json")
|
getBackupFile.launch(JSON)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val JSON = "application/json"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user