mirror of
https://github.com/SimpleMobileTools/Simple-File-Manager.git
synced 2025-02-09 16:38:40 +01:00
Merge pull request #520 from KryptKode/feat/scoped-storage
Feat/scoped storage
This commit is contained in:
commit
ba186c0d7f
@ -9,13 +9,12 @@ if (keystorePropertiesFile.exists()) {
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion "29.0.3"
|
||||
compileSdkVersion 30
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.simplemobiletools.filemanager.pro"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
targetSdkVersion 30
|
||||
versionCode 110
|
||||
versionName "6.10.1"
|
||||
multiDexEnabled true
|
||||
@ -58,7 +57,7 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.github.SimpleMobileTools:Simple-Commons:c184f98ca8'
|
||||
implementation 'com.github.SimpleMobileTools:Simple-Commons:4e6eeb901f'
|
||||
implementation 'com.github.Stericson:RootTools:df729dcb13'
|
||||
implementation 'com.github.Stericson:RootShell:1.6'
|
||||
implementation 'com.alexvasilkov:gesture-views:2.5.2'
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.faketouch"
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.simplemobiletools.filemanager.pro.activities
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.SearchManager
|
||||
import android.content.ClipData
|
||||
@ -8,13 +9,18 @@ import android.content.Intent
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.media.RingtoneManager
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.os.Handler
|
||||
import android.provider.Settings
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.core.view.MenuItemCompat
|
||||
import androidx.viewpager.widget.ViewPager
|
||||
import com.simplemobiletools.commons.dialogs.ConfirmationAdvancedDialog
|
||||
import com.simplemobiletools.commons.dialogs.ConfirmationDialog
|
||||
import com.simplemobiletools.commons.dialogs.RadioGroupDialog
|
||||
import com.simplemobiletools.commons.extensions.*
|
||||
import com.simplemobiletools.commons.helpers.*
|
||||
@ -46,6 +52,7 @@ import java.util.*
|
||||
|
||||
class MainActivity : SimpleActivity() {
|
||||
private val BACK_PRESS_TIMEOUT = 5000
|
||||
private val MANAGE_STORAGE_RC = 201
|
||||
private val PICKED_PATH = "picked_path"
|
||||
private var isSearchOpen = false
|
||||
private var wasBackJustPressed = false
|
||||
@ -282,8 +289,8 @@ class MainActivity : SimpleActivity() {
|
||||
}
|
||||
|
||||
private fun tryInitFileManager() {
|
||||
val hadPermission = hasPermission(PERMISSION_WRITE_STORAGE)
|
||||
handlePermission(PERMISSION_WRITE_STORAGE) {
|
||||
val hadPermission = hasStoragePermission()
|
||||
handleStoragePermission {
|
||||
checkOTGPath()
|
||||
if (it) {
|
||||
if (main_view_pager.adapter == null) {
|
||||
@ -300,6 +307,56 @@ class MainActivity : SimpleActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
private fun handleStoragePermission(callback: (granted: Boolean) -> Unit) {
|
||||
actionOnPermission = null
|
||||
if (hasStoragePermission()) {
|
||||
callback(true)
|
||||
} else {
|
||||
if (isRPlus()) {
|
||||
ConfirmationAdvancedDialog(this, "", R.string.access_storage_prompt, R.string.ok, 0) { success ->
|
||||
if (success ) {
|
||||
isAskingPermissions = true
|
||||
actionOnPermission = callback
|
||||
try {
|
||||
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
|
||||
intent.addCategory("android.intent.category.DEFAULT")
|
||||
intent.data = Uri.parse("package:$packageName")
|
||||
startActivityForResult(intent, MANAGE_STORAGE_RC)
|
||||
} catch (e: Exception) {
|
||||
showErrorToast(e)
|
||||
val intent = Intent()
|
||||
intent.action = Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION
|
||||
startActivityForResult(intent, MANAGE_STORAGE_RC)
|
||||
}
|
||||
} else {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
handlePermission(PERMISSION_WRITE_STORAGE, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
private fun hasStoragePermission(): Boolean {
|
||||
return if (isRPlus()) {
|
||||
Environment.isExternalStorageManager()
|
||||
} else {
|
||||
hasPermission(PERMISSION_WRITE_STORAGE)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, resultData)
|
||||
isAskingPermissions = false
|
||||
if (requestCode == MANAGE_STORAGE_RC && isRPlus()) {
|
||||
actionOnPermission?.invoke(Environment.isExternalStorageManager())
|
||||
}
|
||||
}
|
||||
|
||||
private fun initFileManager(refreshRecents: Boolean) {
|
||||
if (intent.action == Intent.ACTION_VIEW && intent.data != null) {
|
||||
val data = intent.data
|
||||
|
@ -41,6 +41,7 @@ import com.simplemobiletools.filemanager.pro.helpers.*
|
||||
import com.simplemobiletools.filemanager.pro.interfaces.ItemOperationsListener
|
||||
import com.simplemobiletools.filemanager.pro.models.ListItem
|
||||
import com.stericson.RootTools.RootTools
|
||||
import java.io.BufferedInputStream
|
||||
import kotlinx.android.synthetic.main.item_file_dir_grid.view.*
|
||||
import kotlinx.android.synthetic.main.item_file_dir_list.view.*
|
||||
import kotlinx.android.synthetic.main.item_file_dir_list.view.item_frame
|
||||
@ -49,10 +50,9 @@ import kotlinx.android.synthetic.main.item_file_dir_list.view.item_name
|
||||
import kotlinx.android.synthetic.main.item_section.view.*
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.util.*
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipFile
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
class ItemsAdapter(
|
||||
@ -329,13 +329,25 @@ class ItemsAdapter(
|
||||
private fun addFileUris(path: String, paths: ArrayList<String>) {
|
||||
if (activity.getIsPathDirectory(path)) {
|
||||
val shouldShowHidden = activity.config.shouldShowHidden
|
||||
if (activity.isPathOnOTG(path)) {
|
||||
activity.getDocumentFile(path)?.listFiles()?.filter { if (shouldShowHidden) true else !it.name!!.startsWith(".") }?.forEach {
|
||||
addFileUris(it.uri.toString(), paths)
|
||||
when {
|
||||
activity.isRestrictedSAFOnlyRoot(path) -> {
|
||||
activity.getAndroidSAFFileItems(path, shouldShowHidden, false) { files ->
|
||||
files.forEach {
|
||||
addFileUris(activity.getAndroidSAFUri(it.path).toString(), paths)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
File(path).listFiles()?.filter { if (shouldShowHidden) true else !it.name.startsWith('.') }?.forEach {
|
||||
addFileUris(it.absolutePath, paths)
|
||||
|
||||
activity.isPathOnOTG(path) -> {
|
||||
activity.getDocumentFile(path)?.listFiles()?.filter { if (shouldShowHidden) true else !it.name!!.startsWith(".") }?.forEach {
|
||||
addFileUris(it.uri.toString(), paths)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
File(path).listFiles()?.filter { if (shouldShowHidden) true else !it.name.startsWith('.') }?.forEach {
|
||||
addFileUris(it.absolutePath, paths)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -390,20 +402,30 @@ class ItemsAdapter(
|
||||
activity.copyMoveFilesTo(files, source, it, isCopyOperation, false, activity.config.shouldShowHidden) {
|
||||
if (!isCopyOperation) {
|
||||
files.forEach { sourceFileDir ->
|
||||
val sourceFile = File(sourceFileDir.path)
|
||||
if (activity.getDoesFilePathExist(source) && activity.getIsPathDirectory(source) &&
|
||||
sourceFile.list()?.isEmpty() == true && sourceFile.getProperSize(true) == 0L && sourceFile.getFileCount(true) == 0
|
||||
) {
|
||||
val sourceFolder = sourceFile.toFileDirItem(activity)
|
||||
activity.deleteFile(sourceFolder, true) {
|
||||
val sourcePath = sourceFileDir.path
|
||||
if (activity.isRestrictedSAFOnlyRoot(sourcePath) && activity.getDoesFilePathExist(sourcePath)) {
|
||||
activity.deleteFile(sourceFileDir, true) {
|
||||
listener?.refreshFragment()
|
||||
activity.runOnUiThread {
|
||||
finishActMode()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
listener?.refreshFragment()
|
||||
finishActMode()
|
||||
val sourceFile = File(sourcePath)
|
||||
if (activity.getDoesFilePathExist(source) && activity.getIsPathDirectory(source) &&
|
||||
sourceFile.list()?.isEmpty() == true && sourceFile.getProperSize(true) == 0L && sourceFile.getFileCount(true) == 0
|
||||
) {
|
||||
val sourceFolder = sourceFile.toFileDirItem(activity)
|
||||
activity.deleteFile(sourceFolder, true) {
|
||||
listener?.refreshFragment()
|
||||
activity.runOnUiThread {
|
||||
finishActMode()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
listener?.refreshFragment()
|
||||
finishActMode()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -443,22 +465,27 @@ class ItemsAdapter(
|
||||
|
||||
CompressAsDialog(activity, firstPath) {
|
||||
val destination = it
|
||||
activity.handleSAFDialog(firstPath) {
|
||||
if (!it) {
|
||||
return@handleSAFDialog
|
||||
activity.handleAndroidSAFDialog(firstPath) { granted ->
|
||||
if (!granted) {
|
||||
return@handleAndroidSAFDialog
|
||||
}
|
||||
activity.handleSAFDialog(firstPath) {
|
||||
if (!it) {
|
||||
return@handleSAFDialog
|
||||
}
|
||||
|
||||
activity.toast(R.string.compressing)
|
||||
val paths = getSelectedFileDirItems().map { it.path }
|
||||
ensureBackgroundThread {
|
||||
if (compressPaths(paths, destination)) {
|
||||
activity.runOnUiThread {
|
||||
activity.toast(R.string.compression_successful)
|
||||
listener?.refreshFragment()
|
||||
finishActMode()
|
||||
activity.toast(R.string.compressing)
|
||||
val paths = getSelectedFileDirItems().map { it.path }
|
||||
ensureBackgroundThread {
|
||||
if (compressPaths(paths, destination)) {
|
||||
activity.runOnUiThread {
|
||||
activity.toast(R.string.compression_successful)
|
||||
listener?.refreshFragment()
|
||||
finishActMode()
|
||||
}
|
||||
} else {
|
||||
activity.toast(R.string.compressing_failed)
|
||||
}
|
||||
} else {
|
||||
activity.toast(R.string.compressing_failed)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -478,103 +505,108 @@ class ItemsAdapter(
|
||||
}
|
||||
|
||||
val paths = getSelectedFileDirItems().asSequence().map { it.path }.filter { it.isZipFile() }.toList()
|
||||
tryDecompressingPaths(paths) {
|
||||
if (it) {
|
||||
activity.toast(R.string.decompression_successful)
|
||||
ensureBackgroundThread {
|
||||
tryDecompressingPaths(paths) { success ->
|
||||
activity.runOnUiThread {
|
||||
listener?.refreshFragment()
|
||||
finishActMode()
|
||||
if (success) {
|
||||
activity.toast(R.string.decompression_successful)
|
||||
listener?.refreshFragment()
|
||||
finishActMode()
|
||||
} else {
|
||||
activity.toast(R.string.decompressing_failed)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
activity.toast(R.string.decompressing_failed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun tryDecompressingPaths(sourcePaths: List<String>, callback: (success: Boolean) -> Unit) {
|
||||
sourcePaths.forEach {
|
||||
try {
|
||||
val zipFile = ZipFile(it)
|
||||
val entries = zipFile.entries()
|
||||
val fileDirItems = ArrayList<FileDirItem>()
|
||||
while (entries.hasMoreElements()) {
|
||||
val entry = entries.nextElement()
|
||||
val currPath = if (entry.isDirectory) it else "${it.getParentPath().trimEnd('/')}/${entry.name}"
|
||||
val fileDirItem = FileDirItem(currPath, entry.name, entry.isDirectory, 0, entry.size)
|
||||
fileDirItems.add(fileDirItem)
|
||||
}
|
||||
|
||||
val destinationPath = fileDirItems.first().getParentPath().trimEnd('/')
|
||||
activity.checkConflicts(fileDirItems, destinationPath, 0, LinkedHashMap()) {
|
||||
ensureBackgroundThread {
|
||||
decompressPaths(sourcePaths, it, callback)
|
||||
sourcePaths.forEach { path ->
|
||||
ZipInputStream(BufferedInputStream(activity.getFileInputStreamSync(path))).use { zipInputStream ->
|
||||
try {
|
||||
val fileDirItems = ArrayList<FileDirItem>()
|
||||
var entry = zipInputStream.nextEntry
|
||||
while (entry != null) {
|
||||
val currPath = if (entry.isDirectory) path else "${path.getParentPath().trimEnd('/')}/${entry.name}"
|
||||
val fileDirItem = FileDirItem(currPath, entry.name, entry.isDirectory, 0, entry.size)
|
||||
fileDirItems.add(fileDirItem)
|
||||
zipInputStream.closeEntry()
|
||||
entry = zipInputStream.nextEntry
|
||||
}
|
||||
zipInputStream.closeEntry()
|
||||
val destinationPath = fileDirItems.first().getParentPath().trimEnd('/')
|
||||
activity.checkConflicts(fileDirItems, destinationPath, 0, LinkedHashMap()) {
|
||||
ensureBackgroundThread {
|
||||
decompressPaths(sourcePaths, it, callback)
|
||||
}
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
activity.showErrorToast(exception)
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
activity.showErrorToast(exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun decompressPaths(paths: List<String>, conflictResolutions: LinkedHashMap<String, Int>, callback: (success: Boolean) -> Unit) {
|
||||
paths.forEach {
|
||||
try {
|
||||
val zipFile = ZipFile(it)
|
||||
val entries = zipFile.entries()
|
||||
val zipFileName = it.getFilenameFromPath()
|
||||
val newFolderName = zipFileName.subSequence(0, zipFileName.length - 4)
|
||||
while (entries.hasMoreElements()) {
|
||||
val entry = entries.nextElement()
|
||||
val parentPath = it.getParentPath()
|
||||
val newPath = "$parentPath/$newFolderName/${entry.name.trimEnd('/')}"
|
||||
paths.forEach { path ->
|
||||
val zipInputStream = ZipInputStream(BufferedInputStream(activity.getFileInputStreamSync(path)))
|
||||
zipInputStream.use {
|
||||
try {
|
||||
var entry = zipInputStream.nextEntry
|
||||
val zipFileName = path.getFilenameFromPath()
|
||||
val newFolderName = zipFileName.subSequence(0, zipFileName.length - 4)
|
||||
while (entry != null) {
|
||||
val parentPath = path.getParentPath()
|
||||
val newPath = "$parentPath/$newFolderName/${entry.name.trimEnd('/')}"
|
||||
|
||||
val resolution = getConflictResolution(conflictResolutions, newPath)
|
||||
val doesPathExist = activity.getDoesFilePathExist(newPath)
|
||||
if (doesPathExist && resolution == CONFLICT_OVERWRITE) {
|
||||
val fileDirItem = FileDirItem(newPath, newPath.getFilenameFromPath(), entry.isDirectory)
|
||||
if (activity.getIsPathDirectory(it)) {
|
||||
activity.deleteFolderBg(fileDirItem, false) {
|
||||
if (it) {
|
||||
extractEntry(newPath, entry, zipFile)
|
||||
} else {
|
||||
callback(false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
activity.deleteFileBg(fileDirItem, false) {
|
||||
if (it) {
|
||||
extractEntry(newPath, entry, zipFile)
|
||||
} else {
|
||||
callback(false)
|
||||
val resolution = getConflictResolution(conflictResolutions, newPath)
|
||||
val doesPathExist = activity.getDoesFilePathExist(newPath)
|
||||
if (doesPathExist && resolution == CONFLICT_OVERWRITE) {
|
||||
val fileDirItem = FileDirItem(newPath, newPath.getFilenameFromPath(), entry.isDirectory)
|
||||
if (activity.getIsPathDirectory(path)) {
|
||||
activity.deleteFolderBg(fileDirItem, false) {
|
||||
if (it) {
|
||||
extractEntry(newPath, entry, zipInputStream)
|
||||
} else {
|
||||
callback(false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
activity.deleteFileBg(fileDirItem, false) {
|
||||
if (it) {
|
||||
extractEntry(newPath, entry, zipInputStream)
|
||||
} else {
|
||||
callback(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!doesPathExist) {
|
||||
extractEntry(newPath, entry, zipInputStream)
|
||||
}
|
||||
} else if (!doesPathExist) {
|
||||
extractEntry(newPath, entry, zipFile)
|
||||
|
||||
zipInputStream.closeEntry()
|
||||
entry = zipInputStream.nextEntry
|
||||
}
|
||||
callback(true)
|
||||
} catch (e: Exception) {
|
||||
activity.showErrorToast(e)
|
||||
callback(false)
|
||||
}
|
||||
callback(true)
|
||||
} catch (e: Exception) {
|
||||
activity.showErrorToast(e)
|
||||
callback(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractEntry(newPath: String, entry: ZipEntry, zipFile: ZipFile) {
|
||||
private fun extractEntry(newPath: String, entry: ZipEntry, zipInputStream: ZipInputStream) {
|
||||
if (entry.isDirectory) {
|
||||
if (!activity.createDirectorySync(newPath) && !activity.getDoesFilePathExist(newPath)) {
|
||||
val error = String.format(activity.getString(R.string.could_not_create_file), newPath)
|
||||
activity.showErrorToast(error)
|
||||
}
|
||||
} else {
|
||||
val ins = zipFile.getInputStream(entry)
|
||||
ins.use {
|
||||
val fos = activity.getFileOutputStreamSync(newPath, newPath.getMimeType())
|
||||
if (fos != null) {
|
||||
ins.copyTo(fos)
|
||||
}
|
||||
val fos = activity.getFileOutputStreamSync(newPath, newPath.getMimeType())
|
||||
if (fos != null) {
|
||||
zipInputStream.copyTo(fos)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -590,43 +622,62 @@ class ItemsAdapter(
|
||||
}
|
||||
|
||||
private fun compressPaths(sourcePaths: List<String>, targetPath: String): Boolean {
|
||||
val queue = LinkedList<File>()
|
||||
val queue = LinkedList<String>()
|
||||
val fos = activity.getFileOutputStreamSync(targetPath, "application/zip") ?: return false
|
||||
|
||||
val zout = ZipOutputStream(fos)
|
||||
var res: Closeable = fos
|
||||
|
||||
try {
|
||||
sourcePaths.forEach {
|
||||
sourcePaths.forEach { currentPath ->
|
||||
var name: String
|
||||
var mainFile = File(it)
|
||||
val base = mainFile.parentFile.toURI()
|
||||
var mainFilePath = currentPath
|
||||
val base = "${mainFilePath.getParentPath()}/"
|
||||
res = zout
|
||||
queue.push(mainFile)
|
||||
if (activity.getIsPathDirectory(mainFile.absolutePath)) {
|
||||
name = "${mainFile.name.trimEnd('/')}/"
|
||||
queue.push(mainFilePath)
|
||||
if (activity.getIsPathDirectory(mainFilePath)) {
|
||||
name = "${mainFilePath.getFilenameFromPath()}/"
|
||||
zout.putNextEntry(ZipEntry(name))
|
||||
}
|
||||
|
||||
while (!queue.isEmpty()) {
|
||||
mainFile = queue.pop()
|
||||
if (activity.getIsPathDirectory(mainFile.absolutePath)) {
|
||||
for (file in mainFile.listFiles()) {
|
||||
name = base.relativize(file.toURI()).path
|
||||
if (activity.getIsPathDirectory(file.absolutePath)) {
|
||||
queue.push(file)
|
||||
name = "${name.trimEnd('/')}/"
|
||||
zout.putNextEntry(ZipEntry(name))
|
||||
} else {
|
||||
zout.putNextEntry(ZipEntry(name))
|
||||
FileInputStream(file).copyTo(zout)
|
||||
zout.closeEntry()
|
||||
mainFilePath = queue.pop()
|
||||
if (activity.getIsPathDirectory(mainFilePath)) {
|
||||
if (activity.isRestrictedSAFOnlyRoot(mainFilePath)) {
|
||||
activity.getAndroidSAFFileItems(mainFilePath, true) { files ->
|
||||
for (file in files) {
|
||||
name = file.path.relativizeWith(base)
|
||||
if (activity.getIsPathDirectory(file.path)) {
|
||||
queue.push(file.path)
|
||||
name = "${name.trimEnd('/')}/"
|
||||
zout.putNextEntry(ZipEntry(name))
|
||||
} else {
|
||||
zout.putNextEntry(ZipEntry(name))
|
||||
activity.getFileInputStreamSync(file.path)!!.copyTo(zout)
|
||||
zout.closeEntry()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val mainFile = File(mainFilePath)
|
||||
for (file in mainFile.listFiles()) {
|
||||
name = file.path.relativizeWith(base)
|
||||
if (activity.getIsPathDirectory(file.absolutePath)) {
|
||||
queue.push(file.absolutePath)
|
||||
name = "${name.trimEnd('/')}/"
|
||||
zout.putNextEntry(ZipEntry(name))
|
||||
} else {
|
||||
zout.putNextEntry(ZipEntry(name))
|
||||
activity.getFileInputStreamSync(file.path)!!.copyTo(zout)
|
||||
zout.closeEntry()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
name = if (base.path == it) it.getFilenameFromPath() else base.relativize(mainFile.toURI()).path
|
||||
name = if (base == currentPath) currentPath.getFilenameFromPath() else mainFilePath.relativizeWith(base)
|
||||
zout.putNextEntry(ZipEntry(name))
|
||||
FileInputStream(mainFile).copyTo(zout)
|
||||
activity.getFileInputStreamSync(mainFilePath)!!.copyTo(zout)
|
||||
zout.closeEntry()
|
||||
}
|
||||
}
|
||||
@ -833,7 +884,9 @@ class ItemsAdapter(
|
||||
path
|
||||
}
|
||||
|
||||
if (hasOTGConnected && itemToLoad is String && activity.isPathOnOTG(itemToLoad) && baseConfig.OTGTreeUri.isNotEmpty() && baseConfig.OTGPartition.isNotEmpty()) {
|
||||
if (activity.isRestrictedSAFOnlyRoot(path)) {
|
||||
itemToLoad = activity.getAndroidSAFUri(path)
|
||||
} else if (hasOTGConnected && itemToLoad is String && activity.isPathOnOTG(itemToLoad) && baseConfig.OTGTreeUri.isNotEmpty() && baseConfig.OTGPartition.isNotEmpty()) {
|
||||
itemToLoad = getOTGPublicPath(itemToLoad)
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ package com.simplemobiletools.filemanager.pro.dialogs
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.simplemobiletools.commons.extensions.*
|
||||
import com.simplemobiletools.commons.helpers.isRPlus
|
||||
import com.simplemobiletools.filemanager.pro.R
|
||||
import com.simplemobiletools.filemanager.pro.activities.SimpleActivity
|
||||
import com.simplemobiletools.filemanager.pro.helpers.RootHelpers
|
||||
@ -15,41 +16,62 @@ class CreateNewItemDialog(val activity: SimpleActivity, val path: String, val ca
|
||||
|
||||
init {
|
||||
AlertDialog.Builder(activity)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.create().apply {
|
||||
activity.setupDialogStuff(view, this, R.string.create_new) {
|
||||
showKeyboard(view.item_name)
|
||||
getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(View.OnClickListener {
|
||||
val name = view.item_name.value
|
||||
if (name.isEmpty()) {
|
||||
activity.toast(R.string.empty_name)
|
||||
} else if (name.isAValidFilename()) {
|
||||
val newPath = "$path/$name"
|
||||
if (activity.getDoesFilePathExist(newPath)) {
|
||||
activity.toast(R.string.name_taken)
|
||||
return@OnClickListener
|
||||
}
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.create().apply {
|
||||
activity.setupDialogStuff(view, this, R.string.create_new) {
|
||||
showKeyboard(view.item_name)
|
||||
getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(View.OnClickListener {
|
||||
val name = view.item_name.value
|
||||
if (name.isEmpty()) {
|
||||
activity.toast(R.string.empty_name)
|
||||
} else if (name.isAValidFilename()) {
|
||||
val newPath = "$path/$name"
|
||||
if (activity.getDoesFilePathExist(newPath)) {
|
||||
activity.toast(R.string.name_taken)
|
||||
return@OnClickListener
|
||||
}
|
||||
|
||||
if (view.dialog_radio_group.checkedRadioButtonId == R.id.dialog_radio_directory) {
|
||||
createDirectory(newPath, this) {
|
||||
callback(it)
|
||||
}
|
||||
} else {
|
||||
createFile(newPath, this) {
|
||||
callback(it)
|
||||
}
|
||||
if (view.dialog_radio_group.checkedRadioButtonId == R.id.dialog_radio_directory) {
|
||||
createDirectory(newPath, this) {
|
||||
callback(it)
|
||||
}
|
||||
} else {
|
||||
activity.toast(R.string.invalid_name)
|
||||
createFile(newPath, this) {
|
||||
callback(it)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
activity.toast(R.string.invalid_name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createDirectory(path: String, alertDialog: AlertDialog, callback: (Boolean) -> Unit) {
|
||||
when {
|
||||
isRPlus() || path.startsWith(activity.internalStoragePath, true) -> {
|
||||
if (activity.isRestrictedSAFOnlyRoot(path)) {
|
||||
activity.handleAndroidSAFDialog(path) {
|
||||
if (!it) {
|
||||
callback(false)
|
||||
return@handleAndroidSAFDialog
|
||||
}
|
||||
if (activity.createAndroidSAFDirectory(path)) {
|
||||
success(alertDialog)
|
||||
} else {
|
||||
val error = String.format(activity.getString(R.string.could_not_create_folder), path)
|
||||
activity.showErrorToast(error)
|
||||
callback(false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (File(path).mkdirs()) {
|
||||
success(alertDialog)
|
||||
}
|
||||
}
|
||||
}
|
||||
activity.needsStupidWritePermissions(path) -> activity.handleSAFDialog(path) {
|
||||
if (!it) {
|
||||
return@handleSAFDialog
|
||||
@ -65,11 +87,6 @@ class CreateNewItemDialog(val activity: SimpleActivity, val path: String, val ca
|
||||
documentFile.createDirectory(path.getFilenameFromPath())
|
||||
success(alertDialog)
|
||||
}
|
||||
path.startsWith(activity.internalStoragePath, true) -> {
|
||||
if (File(path).mkdirs()) {
|
||||
success(alertDialog)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
RootHelpers(activity).createFileFolder(path, false) {
|
||||
if (it) {
|
||||
@ -85,6 +102,22 @@ class CreateNewItemDialog(val activity: SimpleActivity, val path: String, val ca
|
||||
private fun createFile(path: String, alertDialog: AlertDialog, callback: (Boolean) -> Unit) {
|
||||
try {
|
||||
when {
|
||||
activity.isRestrictedSAFOnlyRoot(path) -> {
|
||||
activity.handleAndroidSAFDialog(path) {
|
||||
if (!it) {
|
||||
callback(false)
|
||||
return@handleAndroidSAFDialog
|
||||
}
|
||||
if (activity.createAndroidSAFFile(path)) {
|
||||
success(alertDialog)
|
||||
} else {
|
||||
val error = String.format(activity.getString(R.string.could_not_create_file), path)
|
||||
activity.showErrorToast(error)
|
||||
callback(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
activity.needsStupidWritePermissions(path) -> {
|
||||
activity.handleSAFDialog(path) {
|
||||
if (!it) {
|
||||
@ -102,7 +135,8 @@ class CreateNewItemDialog(val activity: SimpleActivity, val path: String, val ca
|
||||
success(alertDialog)
|
||||
}
|
||||
}
|
||||
path.startsWith(activity.internalStoragePath, true) -> {
|
||||
|
||||
isRPlus() || path.startsWith(activity.internalStoragePath, true) -> {
|
||||
if (File(path).createNewFile()) {
|
||||
success(alertDialog)
|
||||
}
|
||||
|
@ -58,13 +58,18 @@ class ItemsFragment(context: Context, attributeSet: AttributeSet) : MyViewPagerF
|
||||
initDrawables()
|
||||
}
|
||||
|
||||
breadcrumbs.updateColor(textColor)
|
||||
items_fastscroller.updateBubbleColors()
|
||||
|
||||
if (currentPath != "") {
|
||||
breadcrumbs.updateColor(textColor)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setupFontSize() {
|
||||
getRecyclerAdapter()?.updateFontSizes()
|
||||
breadcrumbs.updateFontSize(context!!.getTextSize())
|
||||
if (currentPath != "") {
|
||||
breadcrumbs.updateFontSize(context!!.getTextSize())
|
||||
}
|
||||
}
|
||||
|
||||
override fun setupDateTimeFormat() {
|
||||
@ -162,7 +167,18 @@ class ItemsFragment(context: Context, attributeSet: AttributeSet) : MyViewPagerF
|
||||
ensureBackgroundThread {
|
||||
if (activity?.isDestroyed == false && activity?.isFinishing == false) {
|
||||
val config = context!!.config
|
||||
if (context!!.isPathOnOTG(path) && config.OTGTreeUri.isNotEmpty()) {
|
||||
if (context.isRestrictedSAFOnlyRoot(path)) {
|
||||
activity?.handleAndroidSAFDialog(path) {
|
||||
if (!it) {
|
||||
activity?.toast(R.string.no_storage_permissions)
|
||||
return@handleAndroidSAFDialog
|
||||
}
|
||||
val getProperChildCount = context!!.config.getFolderViewType(currentPath) == VIEW_TYPE_LIST
|
||||
context.getAndroidSAFFileItems(path, context.config.shouldShowHidden, getProperChildCount) { fileItems ->
|
||||
callback(path, getListItemsFromFileDirItems(fileItems))
|
||||
}
|
||||
}
|
||||
} else if (context!!.isPathOnOTG(path) && config.OTGTreeUri.isNotEmpty()) {
|
||||
val getProperFileSize = context!!.config.getFolderSorting(currentPath) and SORT_BY_SIZE != 0
|
||||
context!!.getOTGItems(path, config.shouldShowHidden, getProperFileSize) {
|
||||
callback(path, getListItemsFromFileDirItems(it))
|
||||
@ -201,7 +217,7 @@ class ItemsFragment(context: Context, attributeSet: AttributeSet) : MyViewPagerF
|
||||
if (getProperChildCount) {
|
||||
items.filter { it.mIsDirectory }.forEach {
|
||||
if (context != null) {
|
||||
val childrenCount = it.getDirectChildrenCount(context!!, showHidden)
|
||||
val childrenCount = it.getDirectChildrenCount(activity as BaseSimpleActivity, showHidden)
|
||||
if (childrenCount != 0) {
|
||||
activity?.runOnUiThread {
|
||||
getRecyclerAdapter()?.updateChildCount(it.mPath, childrenCount)
|
||||
|
@ -1,14 +1,14 @@
|
||||
package com.simplemobiletools.filemanager.pro.fragments
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.provider.MediaStore.Files
|
||||
import android.provider.MediaStore.Files.FileColumns
|
||||
import android.util.AttributeSet
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.simplemobiletools.commons.extensions.*
|
||||
import com.simplemobiletools.commons.helpers.VIEW_TYPE_GRID
|
||||
import com.simplemobiletools.commons.helpers.VIEW_TYPE_LIST
|
||||
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
|
||||
import com.simplemobiletools.commons.helpers.*
|
||||
import com.simplemobiletools.commons.models.FileDirItem
|
||||
import com.simplemobiletools.commons.views.MyGridLayoutManager
|
||||
import com.simplemobiletools.filemanager.pro.R
|
||||
@ -24,6 +24,8 @@ import kotlinx.android.synthetic.main.recents_fragment.view.*
|
||||
import java.util.*
|
||||
|
||||
class RecentsFragment(context: Context, attributeSet: AttributeSet) : MyViewPagerFragment(context, attributeSet), ItemOperationsListener {
|
||||
private val RECENTS_LIMIT = 50
|
||||
|
||||
override fun setupFragment(activity: SimpleActivity) {
|
||||
if (this.activity == null) {
|
||||
this.activity = activity
|
||||
@ -120,17 +122,33 @@ class RecentsFragment(context: Context, attributeSet: AttributeSet) : MyViewPage
|
||||
FileColumns.SIZE
|
||||
)
|
||||
|
||||
val sortOrder = "${FileColumns.DATE_MODIFIED} DESC LIMIT 50"
|
||||
|
||||
context?.queryCursor(uri, projection, sortOrder = sortOrder, showErrors = true) { cursor ->
|
||||
val path = cursor.getStringValue(FileColumns.DATA)
|
||||
val name = cursor.getStringValue(FileColumns.DISPLAY_NAME) ?: path.getFilenameFromPath()
|
||||
val size = cursor.getLongValue(FileColumns.SIZE)
|
||||
val modified = cursor.getLongValue(FileColumns.DATE_MODIFIED) * 1000
|
||||
val fileDirItem = ListItem(path, name, false, 0, size, modified, false)
|
||||
if ((showHidden || !name.startsWith(".")) && activity?.getDoesFilePathExist(path) == true) {
|
||||
listItems.add(fileDirItem)
|
||||
try {
|
||||
if (isOreoPlus()) {
|
||||
val queryArgs = bundleOf(
|
||||
ContentResolver.QUERY_ARG_LIMIT to RECENTS_LIMIT,
|
||||
ContentResolver.QUERY_ARG_SORT_COLUMNS to arrayOf(FileColumns.DATE_MODIFIED),
|
||||
ContentResolver.QUERY_ARG_SORT_DIRECTION to ContentResolver.QUERY_SORT_DIRECTION_DESCENDING
|
||||
)
|
||||
context?.contentResolver?.query(uri, projection, queryArgs, null)
|
||||
} else {
|
||||
val sortOrder = "${FileColumns.DATE_MODIFIED} DESC LIMIT $RECENTS_LIMIT"
|
||||
context?.contentResolver?.query(uri, projection, null, null, sortOrder)
|
||||
}?.use { cursor ->
|
||||
if (cursor.moveToFirst()) {
|
||||
do {
|
||||
val path = cursor.getStringValue(FileColumns.DATA)
|
||||
val name = cursor.getStringValue(FileColumns.DISPLAY_NAME) ?: path.getFilenameFromPath()
|
||||
val size = cursor.getLongValue(FileColumns.SIZE)
|
||||
val modified = cursor.getLongValue(FileColumns.DATE_MODIFIED) * 1000
|
||||
val fileDirItem = ListItem(path, name, false, 0, size, modified, false)
|
||||
if ((showHidden || !name.startsWith(".")) && activity?.getDoesFilePathExist(path) == true) {
|
||||
listItems.add(fileDirItem)
|
||||
}
|
||||
} while (cursor.moveToNext())
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
activity?.showErrorToast(e)
|
||||
}
|
||||
|
||||
activity?.runOnUiThread {
|
||||
|
Loading…
x
Reference in New Issue
Block a user