diff --git a/JAVA_COMPATIBILITY.md b/JAVA_COMPATIBILITY.md index 95ff40c..58591f5 100644 --- a/JAVA_COMPATIBILITY.md +++ b/JAVA_COMPATIBILITY.md @@ -19,6 +19,10 @@ You'll find that the most useful extension functions come from `DocumentFileExtK * `DocumentFile.copyFileTo()` and `File.copyFileTo()` * `DocumentFile.search()` and `File.search()`, etc. +Note that some long-running functions like copy, move, compress, and unzip are now only available in Kotlin. +You can still use these Java features in your project, but you will need [v1.5.6](https://github.com/anggrayudi/SimpleStorage/releases/tag/1.5.6) which is the latest version that +supports Java. + Suppose that you want to get storage ID of the file: #### In Kotlin diff --git a/README.md b/README.md index 2accca3..7354ea8 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,11 @@ allprojects { Simple Storage is built in Kotlin. Follow this [documentation](JAVA_COMPATIBILITY.md) to use it in your Java project. +Note that some long-running functions like copy, move, compress, and unzip are now only available in Kotlin. +They are powered by Kotlin Coroutines & Flow, which are easy to use. +You can still use these Java features in your project, but you will need [v1.5.6](https://github.com/anggrayudi/SimpleStorage/releases/tag/1.5.6) which is the latest version that +supports Java. + ## Terminology ![Alt text](art/terminology.png?raw=true "Simple Storage Terms") @@ -73,7 +78,7 @@ To check whether you have access to particular paths, call `DocumentFileCompat.g ![Alt text](art/getAccessibleAbsolutePaths.png?raw=true "DocumentFileCompat.getAccessibleAbsolutePaths()") All paths in those locations are accessible via functions `DocumentFileCompat.from*()`, otherwise your action will be denied by the system if you want to -access paths other than those. Functions `DocumentFileCompat.from*()` (next section) will return null as well. On API 28-, you can obtain it by requesting +access paths other than those, then functions `DocumentFileCompat.from*()` (next section) will return null as well. On API 28-, you can obtain it by requesting the runtime permission. For API 29+, it is obtained automatically by calling `SimpleStorageHelper#requestStorageAccess()` or `SimpleStorageHelper#openFolderPicker()`. The granted paths are persisted by this library via `ContentResolver#takePersistableUriPermission()`, so you don't need to remember them in preferences: @@ -246,44 +251,46 @@ For example, you can move a folder with few lines of code: val folder: DocumentFile = ... val targetFolder: DocumentFile = ... -// Since moveFolderTo() is annotated with @WorkerThread, you must execute it in the background thread -folder.moveFolderTo(applicationContext, targetFolder, skipEmptyFiles = false, callback = object : FolderCallback() { - override fun onPrepare() { - // Show notification or progress bar dialog with indeterminate state - } - - override fun onCountingFiles() { - // Inform user that the app is counting & calculating files - } - - override fun onStart(folder: DocumentFile, totalFilesToCopy: Int, workerThread: Thread): Long { - return 1000 // update progress every 1 second - } - - override fun onParentConflict(destinationFolder: DocumentFile, action: FolderCallback.ParentFolderConflictAction, canMerge: Boolean) { - handleParentFolderConflict(destinationFolder, action, canMerge) +val job = ioScope.launch { + folder.moveFolderTo(applicationContext, targetFolder, skipEmptyFiles = false, updateInterval = 1000, onConflict = object : FolderConflictCallback(uiScope) { + override fun onParentConflict(destinationFolder: DocumentFile, action: ParentFolderConflictAction, canMerge: Boolean) { + handleParentFolderConflict(destinationFolder, action, canMerge) } override fun onContentConflict( - destinationFolder: DocumentFile, - conflictedFiles: MutableList, - action: FolderCallback.FolderContentConflictAction + destinationFolder: DocumentFile, + conflictedFiles: MutableList, + action: FolderContentConflictAction ) { - handleFolderContentConflict(action, conflictedFiles) + handleFolderContentConflict(action, conflictedFiles) } - - override fun onReport(report: Report) { - Timber.d("onReport() -> ${report.progress.toInt()}% | Copied ${report.fileCount} files") + }).onCompletion { + if (it is CancellationException) { + Timber.d("Folder move is aborted") } - - override fun onCompleted(result: Result) { - Toast.makeText(baseContext, "Copied ${result.totalCopiedFiles} of ${result.totalFilesToCopy} files", Toast.LENGTH_SHORT).show() + }.collect { result -> + when (result) { + is FolderResult.Validating -> Timber.d("Validating...") + is FolderResult.Preparing -> Timber.d("Preparing...") + is FolderResult.CountingFiles -> Timber.d("Counting files...") + is FolderResult.DeletingConflictedFiles -> Timber.d("Deleting conflicted files...") + is FolderResult.Starting -> Timber.d("Starting...") + is FolderResult.InProgress -> Timber.d("Progress: ${result.progress.toInt()}% | ${result.fileCount} files") + is FolderResult.Completed -> uiScope.launch { + Timber.d("Completed: ${result.totalCopiedFiles} of ${result.totalFilesToCopy} files") + Toast.makeText(baseContext, "Moved ${result.totalCopiedFiles} of ${result.totalFilesToCopy} files", Toast.LENGTH_SHORT).show() + } + + is FolderResult.Error -> uiScope.launch { + Timber.e(result.errorCode.name) + Toast.makeText(baseContext, "An error has occurred: ${result.errorCode.name}", Toast.LENGTH_SHORT).show() + } } + } +} - override fun onFailed(errorCode: ErrorCode) { - Toast.makeText(baseContext, "An error has occurred: $errorCode", Toast.LENGTH_SHORT).show() - } -}) +// call this function somewhere, for example in a dialog with a cancel button: +job.cancel() // it will abort the process ``` The coolest thing of this library is you can ask users to choose Merge, Replace, Create New, or Skip Duplicate folders & files