forked from hardcoresushi/DroidFS
Volume copy
This commit is contained in:
parent
bea0906f65
commit
e01b5a3098
@ -1,15 +1,22 @@
|
|||||||
package sushi.hardcore.droidfs
|
package sushi.hardcore.droidfs
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.ServiceConnection
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.IBinder
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import sushi.hardcore.droidfs.adapters.VolumeAdapter
|
import sushi.hardcore.droidfs.adapters.VolumeAdapter
|
||||||
import sushi.hardcore.droidfs.add_volume.AddVolumeActivity
|
import sushi.hardcore.droidfs.add_volume.AddVolumeActivity
|
||||||
@ -20,6 +27,7 @@ import sushi.hardcore.droidfs.databinding.DialogOpenVolumeBinding
|
|||||||
import sushi.hardcore.droidfs.explorers.ExplorerActivity
|
import sushi.hardcore.droidfs.explorers.ExplorerActivity
|
||||||
import sushi.hardcore.droidfs.explorers.ExplorerActivityDrop
|
import sushi.hardcore.droidfs.explorers.ExplorerActivityDrop
|
||||||
import sushi.hardcore.droidfs.explorers.ExplorerActivityPick
|
import sushi.hardcore.droidfs.explorers.ExplorerActivityPick
|
||||||
|
import sushi.hardcore.droidfs.file_operations.FileOperationService
|
||||||
import sushi.hardcore.droidfs.util.PathUtils
|
import sushi.hardcore.droidfs.util.PathUtils
|
||||||
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -35,13 +43,7 @@ class MainActivity : BaseActivity() {
|
|||||||
private var usfKeepOpen: Boolean = false
|
private var usfKeepOpen: Boolean = false
|
||||||
private var addVolume = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
private var addVolume = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
when (result.resultCode) {
|
when (result.resultCode) {
|
||||||
AddVolumeActivity.RESULT_VOLUME_ADDED -> {
|
AddVolumeActivity.RESULT_VOLUME_ADDED -> onVolumeAdded()
|
||||||
volumeAdapter.apply {
|
|
||||||
volumes = volumeDatabase.getVolumes()
|
|
||||||
notifyItemInserted(volumes.size)
|
|
||||||
}
|
|
||||||
binding.textNoVolumes.visibility = View.GONE
|
|
||||||
}
|
|
||||||
AddVolumeActivity.RESULT_HASH_STORAGE_RESET -> {
|
AddVolumeActivity.RESULT_HASH_STORAGE_RESET -> {
|
||||||
volumeAdapter.refresh()
|
volumeAdapter.refresh()
|
||||||
binding.textNoVolumes.visibility = View.GONE
|
binding.textNoVolumes.visibility = View.GONE
|
||||||
@ -50,12 +52,13 @@ class MainActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
private var changePasswordPosition: Int? = null
|
private var changePasswordPosition: Int? = null
|
||||||
private var changePassword = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
private var changePassword = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
changePasswordPosition?.let {
|
changePasswordPosition?.let { unselect(it) }
|
||||||
volumeAdapter.selectedItems.remove(it)
|
|
||||||
volumeAdapter.onVolumeChanged(it)
|
|
||||||
}
|
|
||||||
invalidateOptionsMenu()
|
|
||||||
}
|
}
|
||||||
|
private val pickDirectory = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri ->
|
||||||
|
if (uri != null)
|
||||||
|
onDirectoryPicked(uri)
|
||||||
|
}
|
||||||
|
private lateinit var fileOperationService: FileOperationService
|
||||||
private var pickMode = false
|
private var pickMode = false
|
||||||
private var dropMode = false
|
private var dropMode = false
|
||||||
private var shouldCloseVolume = true // used when launched to pick file from another volume
|
private var shouldCloseVolume = true // used when launched to pick file from another volume
|
||||||
@ -107,6 +110,14 @@ class MainActivity : BaseActivity() {
|
|||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
fingerprintProtector = FingerprintProtector.new(this, themeValue, volumeDatabase)
|
fingerprintProtector = FingerprintProtector.new(this, themeValue, volumeDatabase)
|
||||||
}
|
}
|
||||||
|
Intent(this, FileOperationService::class.java).also {
|
||||||
|
bindService(it, object : ServiceConnection {
|
||||||
|
override fun onServiceConnected(className: ComponentName, service: IBinder) {
|
||||||
|
fileOperationService = (service as FileOperationService.LocalBinder).getService()
|
||||||
|
}
|
||||||
|
override fun onServiceDisconnected(arg0: ComponentName) {}
|
||||||
|
}, Context.BIND_AUTO_CREATE)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onVolumeItemClick(volume: Volume, position: Int) {
|
private fun onVolumeItemClick(volume: Volume, position: Int) {
|
||||||
@ -120,11 +131,25 @@ class MainActivity : BaseActivity() {
|
|||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onVolumeAdded() {
|
||||||
|
volumeAdapter.apply {
|
||||||
|
volumes = volumeDatabase.getVolumes()
|
||||||
|
notifyItemInserted(volumes.size)
|
||||||
|
}
|
||||||
|
binding.textNoVolumes.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
private fun unselectAll() {
|
private fun unselectAll() {
|
||||||
volumeAdapter.unSelectAll()
|
volumeAdapter.unSelectAll()
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun unselect(position: Int) {
|
||||||
|
volumeAdapter.selectedItems.remove(position)
|
||||||
|
volumeAdapter.onVolumeChanged(position)
|
||||||
|
invalidateOptionsMenu()
|
||||||
|
}
|
||||||
|
|
||||||
private fun removeVolumes(volumes: List<Volume>, i: Int = 0, doDeleteVolumeContent: Boolean? = null) {
|
private fun removeVolumes(volumes: List<Volume>, i: Int = 0, doDeleteVolumeContent: Boolean? = null) {
|
||||||
if (i < volumes.size) {
|
if (i < volumes.size) {
|
||||||
if (volumes[i].isHidden) {
|
if (volumes[i].isHidden) {
|
||||||
@ -212,6 +237,32 @@ class MainActivity : BaseActivity() {
|
|||||||
})
|
})
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
R.id.copy -> {
|
||||||
|
val position = volumeAdapter.selectedItems.elementAt(0)
|
||||||
|
val volume = volumeAdapter.volumes[position]
|
||||||
|
when {
|
||||||
|
volume.isHidden -> {
|
||||||
|
PathUtils.safePickDirectory(pickDirectory, this, themeValue)
|
||||||
|
}
|
||||||
|
File(filesDir, volume.shortName).exists() -> {
|
||||||
|
CustomAlertDialogBuilder(this, themeValue)
|
||||||
|
.setTitle(R.string.error)
|
||||||
|
.setMessage(R.string.hidden_volume_already_exists)
|
||||||
|
.setPositiveButton(R.string.ok, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
unselect(position)
|
||||||
|
copyVolume(
|
||||||
|
DocumentFile.fromFile(File(volume.name)),
|
||||||
|
DocumentFile.fromFile(filesDir),
|
||||||
|
) {
|
||||||
|
Volume(volume.shortName, true, volume.encryptedHash, volume.iv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
R.id.settings -> {
|
R.id.settings -> {
|
||||||
val intent = Intent(this, SettingsActivity::class.java)
|
val intent = Intent(this, SettingsActivity::class.java)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
@ -241,10 +292,72 @@ class MainActivity : BaseActivity() {
|
|||||||
!pickMode && !dropMode &&
|
!pickMode && !dropMode &&
|
||||||
volumeAdapter.selectedItems.size == 1 &&
|
volumeAdapter.selectedItems.size == 1 &&
|
||||||
volumeAdapter.volumes[volumeAdapter.selectedItems.elementAt(0)].canWrite(filesDir.path)
|
volumeAdapter.volumes[volumeAdapter.selectedItems.elementAt(0)].canWrite(filesDir.path)
|
||||||
|
with(menu.findItem(R.id.copy)) {
|
||||||
|
isVisible = !pickMode && !dropMode && volumeAdapter.selectedItems.size == 1
|
||||||
|
if (isVisible) {
|
||||||
|
setTitle(if (volumeAdapter.volumes[volumeAdapter.selectedItems.elementAt(0)].isHidden)
|
||||||
|
R.string.copy_hidden_volume
|
||||||
|
else
|
||||||
|
R.string.copy_external_volume
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(isSelecting || pickMode || dropMode)
|
supportActionBar?.setDisplayHomeAsUpEnabled(isSelecting || pickMode || dropMode)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onDirectoryPicked(uri: Uri) {
|
||||||
|
val position = volumeAdapter.selectedItems.elementAt(0)
|
||||||
|
val volume = volumeAdapter.volumes[position]
|
||||||
|
unselect(position)
|
||||||
|
val dstDocumentFile = DocumentFile.fromTreeUri(this, uri)
|
||||||
|
if (dstDocumentFile == null) {
|
||||||
|
CustomAlertDialogBuilder(this, themeValue)
|
||||||
|
.setTitle(R.string.error)
|
||||||
|
.setMessage(R.string.path_error)
|
||||||
|
.setPositiveButton(R.string.ok, null)
|
||||||
|
.show()
|
||||||
|
} else {
|
||||||
|
copyVolume(
|
||||||
|
DocumentFile.fromFile(File(volume.getFullPath(filesDir.path))),
|
||||||
|
dstDocumentFile,
|
||||||
|
) { dstRootDirectory ->
|
||||||
|
dstRootDirectory.name?.let { name ->
|
||||||
|
val path = PathUtils.getFullPathFromTreeUri(dstRootDirectory.uri, this)
|
||||||
|
if (path == null) null
|
||||||
|
else Volume(
|
||||||
|
PathUtils.pathJoin(path, name),
|
||||||
|
false,
|
||||||
|
volume.encryptedHash,
|
||||||
|
volume.iv
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun copyVolume(srcDocumentFile: DocumentFile, dstDocumentFile: DocumentFile, getResultVolume: (DocumentFile) -> Volume?) {
|
||||||
|
fileOperationService.copyVolume(srcDocumentFile, dstDocumentFile) { dstRootDirectory, failedItem ->
|
||||||
|
runOnUiThread {
|
||||||
|
if (failedItem == null) {
|
||||||
|
dstRootDirectory?.let {
|
||||||
|
getResultVolume(it)?.let { volume ->
|
||||||
|
volumeDatabase.saveVolume(volume)
|
||||||
|
onVolumeAdded()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Toast.makeText(this, R.string.copy_success, Toast.LENGTH_SHORT).show()
|
||||||
|
} else {
|
||||||
|
CustomAlertDialogBuilder(this, themeValue)
|
||||||
|
.setTitle(R.string.error)
|
||||||
|
.setMessage(getString(R.string.copy_failed, failedItem.name))
|
||||||
|
.setPositiveButton(R.string.ok, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("NewApi") // fingerprintProtector is non-null only when SDK_INT >= 23
|
@SuppressLint("NewApi") // fingerprintProtector is non-null only when SDK_INT >= 23
|
||||||
private fun openVolume(volume: Volume, position: Int) {
|
private fun openVolume(volume: Volume, position: Int) {
|
||||||
var askForPassword = true
|
var askForPassword = true
|
||||||
|
@ -2,7 +2,6 @@ package sushi.hardcore.droidfs.add_volume
|
|||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.ActivityNotFoundException
|
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
@ -40,7 +39,7 @@ class SelectPathFragment: Fragment() {
|
|||||||
private lateinit var binding: FragmentSelectPathBinding
|
private lateinit var binding: FragmentSelectPathBinding
|
||||||
private val askStoragePermissions = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
|
private val askStoragePermissions = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
|
||||||
if (result[Manifest.permission.READ_EXTERNAL_STORAGE] == true && result[Manifest.permission.WRITE_EXTERNAL_STORAGE] == true)
|
if (result[Manifest.permission.READ_EXTERNAL_STORAGE] == true && result[Manifest.permission.WRITE_EXTERNAL_STORAGE] == true)
|
||||||
safePickDirectory()
|
PathUtils.safePickDirectory(pickDirectory, requireContext(), themeValue)
|
||||||
else
|
else
|
||||||
CustomAlertDialogBuilder(requireContext(), themeValue)
|
CustomAlertDialogBuilder(requireContext(), themeValue)
|
||||||
.setTitle(R.string.storage_perm_denied)
|
.setTitle(R.string.storage_perm_denied)
|
||||||
@ -87,7 +86,7 @@ class SelectPathFragment: Fragment() {
|
|||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
) == PackageManager.PERMISSION_GRANTED
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
)
|
)
|
||||||
safePickDirectory()
|
PathUtils.safePickDirectory(pickDirectory, requireContext(), themeValue)
|
||||||
else
|
else
|
||||||
askStoragePermissions.launch(
|
askStoragePermissions.launch(
|
||||||
arrayOf(
|
arrayOf(
|
||||||
@ -96,7 +95,7 @@ class SelectPathFragment: Fragment() {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else
|
} else
|
||||||
safePickDirectory()
|
PathUtils.safePickDirectory(pickDirectory, requireContext(), themeValue)
|
||||||
}
|
}
|
||||||
var isVolumeAlreadySaved = false
|
var isVolumeAlreadySaved = false
|
||||||
var volumeAction: Action? = null
|
var volumeAction: Action? = null
|
||||||
@ -145,18 +144,6 @@ class SelectPathFragment: Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun safePickDirectory() {
|
|
||||||
try {
|
|
||||||
pickDirectory.launch(null)
|
|
||||||
} catch (e: ActivityNotFoundException) {
|
|
||||||
CustomAlertDialogBuilder(requireContext(), themeValue)
|
|
||||||
.setTitle(R.string.error)
|
|
||||||
.setMessage(R.string.open_tree_failed)
|
|
||||||
.setPositiveButton(R.string.ok, null)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onDirectoryPicked(uri: Uri) {
|
private fun onDirectoryPicked(uri: Uri) {
|
||||||
val path = PathUtils.getFullPathFromTreeUri(uri, requireContext())
|
val path = PathUtils.getFullPathFromTreeUri(uri, requireContext())
|
||||||
if (path != null)
|
if (path != null)
|
||||||
@ -164,7 +151,7 @@ class SelectPathFragment: Fragment() {
|
|||||||
else
|
else
|
||||||
CustomAlertDialogBuilder(requireContext(), themeValue)
|
CustomAlertDialogBuilder(requireContext(), themeValue)
|
||||||
.setTitle(R.string.error)
|
.setTitle(R.string.error)
|
||||||
.setMessage(R.string.path_from_uri_null_error_msg)
|
.setMessage(R.string.path_error)
|
||||||
.setPositiveButton(R.string.ok, null)
|
.setPositiveButton(R.string.ok, null)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
package sushi.hardcore.droidfs.file_operations
|
package sushi.hardcore.droidfs.file_operations
|
||||||
|
|
||||||
import android.app.*
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.app.Service
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.*
|
import android.os.Binder
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.IBinder
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
@ -385,4 +391,62 @@ class FileOperationService : Service() {
|
|||||||
}
|
}
|
||||||
}.start()
|
}.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun recursiveCountChildElements(rootDirectory: DocumentFile): Int {
|
||||||
|
val children = rootDirectory.listFiles()
|
||||||
|
var count = children.size
|
||||||
|
for (child in children) {
|
||||||
|
if (child.isDirectory) {
|
||||||
|
count += recursiveCountChildElements(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ObjRef<T>(var value: T)
|
||||||
|
|
||||||
|
private fun recursiveCopyVolume(
|
||||||
|
src: DocumentFile,
|
||||||
|
dst: DocumentFile,
|
||||||
|
dstRootDirectory: ObjRef<DocumentFile?>?,
|
||||||
|
notification: FileOperationNotification,
|
||||||
|
total: Int,
|
||||||
|
progress: ObjRef<Int> = ObjRef(0)
|
||||||
|
): DocumentFile? {
|
||||||
|
val dstDir = dst.createDirectory(src.name ?: return src) ?: return src
|
||||||
|
for (child in src.listFiles()) {
|
||||||
|
if (notifications[notification.notificationId]!!) {
|
||||||
|
cancelNotification(notification)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (child.isFile) {
|
||||||
|
val dstFile = dstDir.createFile("", child.name ?: return child) ?: return child
|
||||||
|
val outputStream = contentResolver.openOutputStream(dstFile.uri)
|
||||||
|
val inputStream = contentResolver.openInputStream(child.uri)
|
||||||
|
if (outputStream == null || inputStream == null) return child
|
||||||
|
val written = inputStream.copyTo(outputStream)
|
||||||
|
outputStream.close()
|
||||||
|
inputStream.close()
|
||||||
|
if (written != child.length()) return child
|
||||||
|
} else {
|
||||||
|
recursiveCopyVolume(child, dstDir, null, notification, total, progress)?.let { return it }
|
||||||
|
}
|
||||||
|
progress.value++
|
||||||
|
updateNotificationProgress(notification, progress.value, total)
|
||||||
|
}
|
||||||
|
dstRootDirectory?.let { it.value = dstDir }
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun copyVolume(src: DocumentFile, dst: DocumentFile, callback: (DocumentFile?, DocumentFile?) -> Unit) {
|
||||||
|
Thread {
|
||||||
|
val notification = showNotification(R.string.copy_volume_notification, null)
|
||||||
|
val total = recursiveCountChildElements(src)
|
||||||
|
updateNotificationProgress(notification, 0, total)
|
||||||
|
val dstRootDirectory = ObjRef<DocumentFile?>(null)
|
||||||
|
val failedItem = recursiveCopyVolume(src, dst, dstRootDirectory, notification, total)
|
||||||
|
cancelNotification(notification)
|
||||||
|
callback(dstRootDirectory.value, failedItem)
|
||||||
|
}.start()
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,11 +1,15 @@
|
|||||||
package sushi.hardcore.droidfs.util
|
package sushi.hardcore.droidfs.util
|
||||||
|
|
||||||
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.storage.StorageManager
|
import android.os.storage.StorageManager
|
||||||
import android.provider.DocumentsContract
|
import android.provider.DocumentsContract
|
||||||
import android.provider.OpenableColumns
|
import android.provider.OpenableColumns
|
||||||
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import sushi.hardcore.droidfs.R
|
||||||
|
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
import kotlin.math.log10
|
import kotlin.math.log10
|
||||||
@ -187,4 +191,16 @@ object PathUtils {
|
|||||||
}
|
}
|
||||||
return rootDirectory.delete()
|
return rootDirectory.delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun safePickDirectory(directoryPicker: ActivityResultLauncher<Uri>, context: Context, themeValue: String) {
|
||||||
|
try {
|
||||||
|
directoryPicker.launch(null)
|
||||||
|
} catch (e: ActivityNotFoundException) {
|
||||||
|
CustomAlertDialogBuilder(context, themeValue)
|
||||||
|
.setTitle(R.string.error)
|
||||||
|
.setMessage(R.string.open_tree_failed)
|
||||||
|
.setPositiveButton(R.string.ok, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -34,4 +34,9 @@
|
|||||||
android:visible="false"
|
android:visible="false"
|
||||||
android:title="@string/change_password"/>
|
android:title="@string/change_password"/>
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/copy"
|
||||||
|
app:showAsAction="never"
|
||||||
|
android:visible="false"/>
|
||||||
|
|
||||||
</menu>
|
</menu>
|
@ -128,7 +128,7 @@
|
|||||||
<string name="move_success">Transferência bem sucedida!</string>
|
<string name="move_success">Transferência bem sucedida!</string>
|
||||||
<string name="enter_timer_duration">Definir a duração do tempo (em s)</string>
|
<string name="enter_timer_duration">Definir a duração do tempo (em s)</string>
|
||||||
<string name="timer_empty_error_msg">Por favor, digite um valor numérico</string>
|
<string name="timer_empty_error_msg">Por favor, digite um valor numérico</string>
|
||||||
<string name="path_from_uri_null_error_msg">Falha ao carregar a localização selecionada.</string>
|
<string name="path_error">Falha ao carregar a localização selecionada.</string>
|
||||||
<string name="create_cant_write_error_msg">DroidFS não pode salvar neste local. Por favor, tente algum outro.</string>
|
<string name="create_cant_write_error_msg">DroidFS não pode salvar neste local. Por favor, tente algum outro.</string>
|
||||||
<string name="sdcard_error_header">DroidFS só pode escrever em memórias SD removíveis sob:</string>
|
<string name="sdcard_error_header">DroidFS só pode escrever em memórias SD removíveis sob:</string>
|
||||||
<string name="slideshow_stopped">Apresentação parou</string>
|
<string name="slideshow_stopped">Apresentação parou</string>
|
||||||
|
@ -123,7 +123,7 @@
|
|||||||
<string name="move_success">Перемещение выполнено!</string>
|
<string name="move_success">Перемещение выполнено!</string>
|
||||||
<string name="enter_timer_duration">Введите время таймера (в сек.)</string>
|
<string name="enter_timer_duration">Введите время таймера (в сек.)</string>
|
||||||
<string name="timer_empty_error_msg">Введите числовое значение.</string>
|
<string name="timer_empty_error_msg">Введите числовое значение.</string>
|
||||||
<string name="path_from_uri_null_error_msg">Невозможно получить выбранный путь.</string>
|
<string name="path_error">Невозможно получить выбранный путь.</string>
|
||||||
<string name="create_cant_write_error_msg">DroidFS не имеет доступа на запись по этому пути. Попробуйте найти другое место.</string>
|
<string name="create_cant_write_error_msg">DroidFS не имеет доступа на запись по этому пути. Попробуйте найти другое место.</string>
|
||||||
<string name="sdcard_error_header">DroidFS может записывать на SD-карты только в:</string>
|
<string name="sdcard_error_header">DroidFS может записывать на SD-карты только в:</string>
|
||||||
<string name="slideshow_stopped">Слайдшоу остановлено</string>
|
<string name="slideshow_stopped">Слайдшоу остановлено</string>
|
||||||
|
@ -128,7 +128,7 @@
|
|||||||
<string name="move_success">Move successful !</string>
|
<string name="move_success">Move successful !</string>
|
||||||
<string name="enter_timer_duration">Enter the timer duration (in s)</string>
|
<string name="enter_timer_duration">Enter the timer duration (in s)</string>
|
||||||
<string name="timer_empty_error_msg">Please enter a numeric value</string>
|
<string name="timer_empty_error_msg">Please enter a numeric value</string>
|
||||||
<string name="path_from_uri_null_error_msg">Failed to retrieve the selected path.</string>
|
<string name="path_error">Failed to retrieve the selected path.</string>
|
||||||
<string name="create_cant_write_error_msg">DroidFS doesn\'t have write access to this path. Please try another location.</string>
|
<string name="create_cant_write_error_msg">DroidFS doesn\'t have write access to this path. Please try another location.</string>
|
||||||
<string name="add_read_only">Adding volume with read-only access.</string>
|
<string name="add_read_only">Adding volume with read-only access.</string>
|
||||||
<string name="add_cant_write_warning">DroidFS doesn\'t have write access to this path. Adding volume with read-only access.</string>
|
<string name="add_cant_write_warning">DroidFS doesn\'t have write access to this path. Adding volume with read-only access.</string>
|
||||||
@ -222,4 +222,8 @@
|
|||||||
<string name="new_password_hint">New password</string>
|
<string name="new_password_hint">New password</string>
|
||||||
<string name="new_password_confirmation_label">Repeat the new password:</string>
|
<string name="new_password_confirmation_label">Repeat the new password:</string>
|
||||||
<string name="error_marshmallow_required">This feature is only available on Android 6.0 (Marshmallow) or above.</string>
|
<string name="error_marshmallow_required">This feature is only available on Android 6.0 (Marshmallow) or above.</string>
|
||||||
|
<string name="copy_hidden_volume">Copy to shared storage</string>
|
||||||
|
<string name="copy_external_volume">Make a hidden copy</string>
|
||||||
|
<string name="copy_volume_notification">Copying volume…</string>
|
||||||
|
<string name="hidden_volume_already_exists">A hidden volume with the same name already exists.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
Loading…
Reference in New Issue
Block a user