diff --git a/.all-contributorsrc b/.all-contributorsrc
index 84cba1a..aedaff3 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -65,7 +65,9 @@
"avatar_url": "https://avatars.githubusercontent.com/u/758047?v=4",
"profile": "https://eternityforest.com",
"contributions": [
- "bug"
+ "bug",
+ "code",
+ "doc"
]
}
],
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6d69bb5..7dbd4e7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,22 @@
+## 0.4.0
+
+Fix the current behavior of `listFiles` and `openDocumentFile` API.
+
+### Improvements
+
+- It's now possible to list contents of all subfolders of a granted Uri opened from `openDocumentTree` ([@EternityForest](https://github.com/EternityForest)).
+- Now `ACTION_VIEW` intent builder through `openDocumentFile` API was fixed. So it's now possible to open any file of any kind in third party apps without needing specify the mime type.
+
+### Breaking changes
+
+- Removed Android specific APIs:
+ - `DocumentFile.listFiles` (Now it's only available globally).
+ - `buildDocumentUriUsingTree` removed due high coupling with Android API (Android specific API that are not useful on any other platforms).
+ - `buildDocumentUri` removed due high coupling with Android API (Android specific API that are not useful on any other platforms).
+ - `buildTreeDocumentUri` removed due high coupling with Android API (Android specific API that are not useful on any other platforms).
+- `getDocumentThumbnail` now receives only the `uri` param instead of a `rootUri` and a `documentId`.
+- `rootUri` field from `QueryMetadata` was removed due API ambiguity: there's no such concept in the Android API and this is not required by it to work well.
+
## 0.3.1
Minor improvements and bug fixes:
diff --git a/README.md b/README.md
index 363751c..17cf1c6 100644
--- a/README.md
+++ b/README.md
@@ -22,12 +22,14 @@
## Documentation
-#### See the website for [documentation](https://lakscastro.github.io/shared-storage)
+See the website for [documentation](https://lakscastro.github.io/shared-storage).
All documentation is also available under `/docs` to each released version which is the data source of the website.
You can contribute to the documentation by just editing these files through the GitHub web editor!
+To check all ways you can contribute to this package see [Contributing/Ways to contribute](https://lakscastro.github.io/shared-storage/Contributing/Ways%20to%20contribute/).
+
Latest changes are available on `master` branch and the actual latest published package version lives under `release` branch.
All other branches are derivated from issues, new features or bug fixes.
@@ -46,7 +48,7 @@ These are the brilliant minds behind the development of this plugin!
 www.bibliotecaortodoxa.ro 💻 🐛 🤔 |
 dangilbert 💻 🐛 |
 dhaval-k-simformsolutions 🐛 🤔 |
-  Daniel Dunn 🐛 |
+  Daniel Dunn 🐛 💻 📖 |
diff --git a/android/src/main/kotlin/io/lakscastro/sharedstorage/storageaccessframework/DocumentFileApi.kt b/android/src/main/kotlin/io/lakscastro/sharedstorage/storageaccessframework/DocumentFileApi.kt
index 0806146..af90e89 100644
--- a/android/src/main/kotlin/io/lakscastro/sharedstorage/storageaccessframework/DocumentFileApi.kt
+++ b/android/src/main/kotlin/io/lakscastro/sharedstorage/storageaccessframework/DocumentFileApi.kt
@@ -14,13 +14,13 @@ import io.lakscastro.sharedstorage.ROOT_CHANNEL
import io.lakscastro.sharedstorage.SharedStoragePlugin
import io.lakscastro.sharedstorage.plugin.*
import io.lakscastro.sharedstorage.storageaccessframework.lib.*
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
import java.io.FileNotFoundException
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
/**
* Aimed to implement strictly only the APIs already available from the native and original
@@ -75,57 +75,73 @@ internal class DocumentFileApi(private val plugin: SharedStoragePlugin) :
)
}
PERSISTED_URI_PERMISSIONS ->
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
- persistedUriPermissions(result)
- }
+ persistedUriPermissions(result)
RELEASE_PERSISTABLE_URI_PERMISSION ->
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
- releasePersistableUriPermission(result, call.argument("uri") as String)
- }
+ releasePersistableUriPermission(
+ result,
+ call.argument("uri") as String
+ )
FROM_TREE_URI ->
- if (Build.VERSION.SDK_INT >= API_21) {
- result.success(
- createDocumentFileMap(
- documentFromUri(plugin.context, call.argument("uri") as String)
- )
+ if (Build.VERSION.SDK_INT >= API_21) {
+ result.success(
+ createDocumentFileMap(
+ documentFromUri(
+ plugin.context,
+ call.argument("uri") as String
+ )
)
- }
+ )
+ }
CAN_WRITE ->
- if (Build.VERSION.SDK_INT >= API_21) {
- result.success(
- documentFromUri(plugin.context, call.argument("uri") as String)?.canWrite()
- )
- }
+ if (Build.VERSION.SDK_INT >= API_21) {
+ result.success(
+ documentFromUri(
+ plugin.context,
+ call.argument("uri") as String
+ )?.canWrite()
+ )
+ }
CAN_READ ->
- if (Build.VERSION.SDK_INT >= API_21) {
- val uri = call.argument("uri") as String
+ if (Build.VERSION.SDK_INT >= API_21) {
+ val uri = call.argument("uri") as String
- result.success(documentFromUri(plugin.context, uri)?.canRead())
- }
+ result.success(documentFromUri(plugin.context, uri)?.canRead())
+ }
LENGTH ->
if (Build.VERSION.SDK_INT >= API_21) {
result.success(
- documentFromUri(plugin.context, call.argument("uri") as String)?.length()
+ documentFromUri(
+ plugin.context,
+ call.argument("uri") as String
+ )?.length()
)
}
EXISTS ->
if (Build.VERSION.SDK_INT >= API_21) {
result.success(
- documentFromUri(plugin.context, call.argument("uri") as String)?.exists()
+ documentFromUri(
+ plugin.context,
+ call.argument("uri") as String
+ )?.exists()
)
}
DELETE ->
if (Build.VERSION.SDK_INT >= API_21) {
result.success(
- documentFromUri(plugin.context, call.argument("uri") as String)?.delete()
+ documentFromUri(
+ plugin.context,
+ call.argument("uri") as String
+ )?.delete()
)
}
LAST_MODIFIED ->
if (Build.VERSION.SDK_INT >= API_21) {
- result.success(
- documentFromUri(plugin.context, call.argument("uri") as String)
- ?.lastModified()
+ val document = documentFromUri(
+ plugin.context,
+ call.argument("uri") as String
)
+
+ result.success(document?.lastModified())
}
CREATE_DIRECTORY -> {
if (Build.VERSION.SDK_INT >= API_21) {
@@ -146,7 +162,12 @@ internal class DocumentFileApi(private val plugin: SharedStoragePlugin) :
val displayName = call.argument("displayName") as String
result.success(
- createDocumentFileMap(documentFromUri(plugin.context, uri)?.findFile(displayName))
+ createDocumentFileMap(
+ documentFromUri(
+ plugin.context,
+ uri
+ )?.findFile(displayName)
+ )
)
}
}
@@ -165,9 +186,9 @@ internal class DocumentFileApi(private val plugin: SharedStoragePlugin) :
}
} else {
result.notSupported(
- RENAME_TO,
- API_21,
- mapOf("uri" to "$uri", "destination" to "$destination")
+ RENAME_TO,
+ API_21,
+ mapOf("uri" to "$uri", "destination" to "$destination")
)
}
}
@@ -180,8 +201,13 @@ internal class DocumentFileApi(private val plugin: SharedStoragePlugin) :
val success = renameTo(displayName)
result.success(
- if (success) createDocumentFileMap(documentFromUri(plugin.context, this.uri)!!)
- else null
+ if (success) createDocumentFileMap(
+ documentFromUri(
+ plugin.context,
+ this.uri
+ )!!
+ )
+ else null
)
}
} else {
@@ -235,9 +261,9 @@ internal class DocumentFileApi(private val plugin: SharedStoragePlugin) :
if (Build.VERSION.SDK_INT >= API_26) {
putExtra(
- if (Build.VERSION.SDK_INT >= API_26) DocumentsContract.EXTRA_INITIAL_URI
- else DOCUMENTS_CONTRACT_EXTRA_INITIAL_URI,
- tree?.uri
+ if (Build.VERSION.SDK_INT >= API_26) DocumentsContract.EXTRA_INITIAL_URI
+ else DOCUMENTS_CONTRACT_EXTRA_INITIAL_URI,
+ tree?.uri
)
}
}
@@ -380,7 +406,9 @@ internal class DocumentFileApi(private val plugin: SharedStoragePlugin) :
eventSink = events
when (args["event"]) {
- LIST_FILES -> listFilesEvent(eventSink, args)
+ LIST_FILES -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ listFilesEvent(eventSink, args)
+ }
}
}
@@ -393,28 +421,27 @@ internal class DocumentFileApi(private val plugin: SharedStoragePlugin) :
private fun listFilesEvent(eventSink: EventChannel.EventSink?, args: Map<*, *>) {
if (eventSink == null) return
- val document =
- if (Build.VERSION.SDK_INT >= API_24) {
- documentFromUri(plugin.context, args["uri"] as String) ?: return
- } else {
- null
- }
+ val columns = args["columns"] as List<*>
+ val uri = Uri.parse(args["uri"] as String)
+ val document = DocumentFile.fromTreeUri(plugin.context, uri)
if (document == null) {
eventSink.error(
- EXCEPTION_NOT_SUPPORTED,
- "Android SDK must be greater or equal than [Build.VERSION_CODES.N]",
- "Got (Build.VERSION.SDK_INT): ${Build.VERSION.SDK_INT}"
+ EXCEPTION_NOT_SUPPORTED,
+ "Android SDK must be greater or equal than [Build.VERSION_CODES.N]",
+ "Got (Build.VERSION.SDK_INT): ${Build.VERSION.SDK_INT}"
)
} else {
- val columns = args["columns"] as List<*>
-
if (!document.canRead()) {
val error = "You cannot read a URI that you don't have read permissions"
Log.d("NO PERMISSION!!!", error)
- eventSink.error(EXCEPTION_MISSING_PERMISSIONS, error, mapOf("uri" to args["uri"]))
+ eventSink.error(
+ EXCEPTION_MISSING_PERMISSIONS,
+ error,
+ mapOf("uri" to args["uri"])
+ )
eventSink.endOfStream()
} else {
@@ -422,13 +449,13 @@ internal class DocumentFileApi(private val plugin: SharedStoragePlugin) :
CoroutineScope(Dispatchers.IO).launch {
try {
traverseDirectoryEntries(
- plugin.context.contentResolver,
- rootOnly = true,
- rootUri = document.uri,
- columns =
- columns
- .map { parseDocumentFileColumn(parseDocumentFileColumn(it as String)!!) }
- .toTypedArray()
+ plugin.context.contentResolver,
+ rootOnly = true,
+ targetUri = document.uri,
+ columns =
+ columns
+ .map { parseDocumentFileColumn(parseDocumentFileColumn(it as String)!!) }
+ .toTypedArray()
) { data, _ -> launch(Dispatchers.Main) { eventSink.success(data) } }
} finally {
launch(Dispatchers.Main) { eventSink.endOfStream() }
diff --git a/android/src/main/kotlin/io/lakscastro/sharedstorage/storageaccessframework/DocumentFileHelperApi.kt b/android/src/main/kotlin/io/lakscastro/sharedstorage/storageaccessframework/DocumentFileHelperApi.kt
index 71382a3..e25edd7 100644
--- a/android/src/main/kotlin/io/lakscastro/sharedstorage/storageaccessframework/DocumentFileHelperApi.kt
+++ b/android/src/main/kotlin/io/lakscastro/sharedstorage/storageaccessframework/DocumentFileHelperApi.kt
@@ -1,27 +1,15 @@
package io.lakscastro.sharedstorage.storageaccessframework
import android.content.ActivityNotFoundException
-import android.content.ContentUris
-import android.content.Context
import android.content.Intent
-import android.database.Cursor
import android.net.Uri
-import android.os.Build
-import android.os.Environment
-import android.provider.DocumentsContract
-import android.provider.MediaStore
import android.util.Log
-import androidx.annotation.RequiresApi
-import androidx.core.content.ContextCompat
-import androidx.core.content.PermissionChecker
import io.flutter.plugin.common.*
import io.flutter.plugin.common.EventChannel.StreamHandler
import io.lakscastro.sharedstorage.ROOT_CHANNEL
import io.lakscastro.sharedstorage.SharedStoragePlugin
-import io.lakscastro.sharedstorage.plugin.API_19
import io.lakscastro.sharedstorage.plugin.ActivityListener
import io.lakscastro.sharedstorage.plugin.Listenable
-import io.lakscastro.sharedstorage.plugin.notSupported
import io.lakscastro.sharedstorage.storageaccessframework.lib.*
/**
@@ -53,40 +41,18 @@ internal class DocumentFileHelperApi(private val plugin: SharedStoragePlugin) :
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
OPEN_DOCUMENT_FILE -> openDocumentFile(call, result)
- GET_REAL_PATH_FROM_URI -> getRealPathFromUri(call, result)
else -> result.notImplemented()
}
}
- private fun getRealPathFromUri(call: MethodCall, result: MethodChannel.Result) {
- val uri = Uri.parse(call.argument("uri")!!)
- if (Build.VERSION.SDK_INT >= API_19) {
- result.success(getPath(plugin.context, uri))
- } else {
- result.notSupported(GET_REAL_PATH_FROM_URI, API_19, mapOf("uri" to "$uri"))
- }
- }
-
private fun openDocumentFile(call: MethodCall, result: MethodChannel.Result) {
val uri = Uri.parse(call.argument("uri")!!)
val type = call.argument("type") ?: plugin.context.contentResolver.getType(uri)
val intent =
Intent(Intent.ACTION_VIEW).apply {
- addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
- addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
- addCategory(Intent.CATEGORY_DEFAULT)
-
- val uriWithProviderScheme =
- Uri.Builder().let {
- it.scheme(uri.scheme)
- it.path(uri.path)
- it.query(uri.query)
- it.authority(uri.authority)
- it.build()
- }
-
- setDataAndType(uriWithProviderScheme, type)
+ flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
+ data = uri
}
try {
@@ -116,128 +82,6 @@ internal class DocumentFileHelperApi(private val plugin: SharedStoragePlugin) :
}
}
- private fun hasPermission(permission: String): Boolean {
- return ContextCompat.checkSelfPermission(plugin.binding!!.activity, permission) ==
- PermissionChecker.PERMISSION_GRANTED
- }
-
- /**
- * Get a file path from a Uri. This will get the the path for Storage Access Framework Documents,
- * as well as the _data field for the MediaStore and other file-based ContentProviders.
- *
- * @param context The context.
- * @param uri The Uri to query.
- * @author paulburke
- */
- @RequiresApi(Build.VERSION_CODES.KITKAT)
- fun getPath(context: Context, uri: Uri): String? {
- val isKitKat: Boolean = Build.VERSION.SDK_INT >= API_19
-
- // DocumentProvider
- if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
- // ExternalStorageProvider
- if (isExternalStorageDocument(uri)) {
- val docId: String = DocumentsContract.getDocumentId(uri)
- val split = docId.split(":").toTypedArray()
- val type = split[0]
- if ("primary".equals(type, ignoreCase = true)) {
- return Environment.getExternalStorageDirectory().toString() + "/" + split[1]
- }
-
- // TODO handle non-primary volumes
- } else if (isDownloadsDocument(uri)) {
- val id: String = DocumentsContract.getDocumentId(uri)
- val contentUri: Uri =
- ContentUris.withAppendedId(
- Uri.parse("content://downloads/public_downloads"),
- java.lang.Long.valueOf(id)
- )
-
- return getDataColumn(context, contentUri, null, null)
- } else if (isMediaDocument(uri)) {
- val docId: String = DocumentsContract.getDocumentId(uri)
- val split = docId.split(":").toTypedArray()
-
- val contentUri: Uri? =
- when (split[0]) {
- "image" -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
- "video" -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
- "audio" -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
- else -> null
- }
-
- val selection = "_id=?"
- val selectionArgs = arrayOf(split[1])
-
- contentUri?.let {
- return getDataColumn(context, it, selection, selectionArgs)
- }
- }
- } else if ("content".equals(uri.scheme, ignoreCase = true)) {
- return getDataColumn(context, uri, null, null)
- } else if ("file".equals(uri.scheme, ignoreCase = true)) {
- return uri.path
- }
-
- return null
- }
-
- /**
- * Get the value of the data column for this Uri. This is useful for MediaStore Uris, and other
- * file-based ContentProviders.
- *
- * @param context The context.
- * @param uri The Uri to query.
- * @param selection (Optional) Filter used in the query.
- * @param selectionArgs (Optional) Selection arguments used in the query.
- * @return The value of the _data column, which is typically a file path.
- */
- private fun getDataColumn(
- context: Context,
- uri: Uri,
- selection: String?,
- selectionArgs: Array?
- ): String? {
- var cursor: Cursor? = null
- val column = "_data"
- val projection = arrayOf(column)
- try {
- cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)
- if (cursor != null && cursor.moveToFirst()) {
- val columnIndex: Int = cursor.getColumnIndexOrThrow(column)
-
- return cursor.getString(columnIndex)
- }
- } finally {
- cursor?.close()
- }
- return null
- }
-
- /**
- * @param uri The Uri to check.
- * @return Whether the Uri authority is ExternalStorageProvider.
- */
- private fun isExternalStorageDocument(uri: Uri): Boolean {
- return "com.android.externalstorage.documents" == uri.authority
- }
-
- /**
- * @param uri The Uri to check.
- * @return Whether the Uri authority is DownloadsProvider.
- */
- fun isDownloadsDocument(uri: Uri): Boolean {
- return "com.android.providers.downloads.documents" == uri.authority
- }
-
- /**
- * @param uri The Uri to check.
- * @return Whether the Uri authority is MediaProvider.
- */
- private fun isMediaDocument(uri: Uri): Boolean {
- return "com.android.providers.media.documents" == uri.authority
- }
-
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
when (requestCode) {
/** TODO(@lakscastro): Implement if required */
diff --git a/android/src/main/kotlin/io/lakscastro/sharedstorage/storageaccessframework/DocumentsContractApi.kt b/android/src/main/kotlin/io/lakscastro/sharedstorage/storageaccessframework/DocumentsContractApi.kt
index fb14347..aca2a1b 100644
--- a/android/src/main/kotlin/io/lakscastro/sharedstorage/storageaccessframework/DocumentsContractApi.kt
+++ b/android/src/main/kotlin/io/lakscastro/sharedstorage/storageaccessframework/DocumentsContractApi.kt
@@ -13,7 +13,8 @@ import io.lakscastro.sharedstorage.plugin.API_21
import io.lakscastro.sharedstorage.plugin.ActivityListener
import io.lakscastro.sharedstorage.plugin.Listenable
import io.lakscastro.sharedstorage.plugin.notSupported
-import io.lakscastro.sharedstorage.storageaccessframework.lib.*
+import io.lakscastro.sharedstorage.storageaccessframework.lib.GET_DOCUMENT_THUMBNAIL
+import io.lakscastro.sharedstorage.storageaccessframework.lib.bitmapToBase64
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -30,13 +31,10 @@ internal class DocumentsContractApi(private val plugin: SharedStoragePlugin) :
when (call.method) {
GET_DOCUMENT_THUMBNAIL -> {
if (Build.VERSION.SDK_INT >= API_21) {
- val rootUri = Uri.parse(call.argument("rootUri"))
- val documentId = call.argument("documentId")
+ val uri = Uri.parse(call.argument("uri"))
val width = call.argument("width")!!
val height = call.argument("height")!!
- val uri = DocumentsContract.buildDocumentUriUsingTree(rootUri, documentId)
-
val bitmap =
DocumentsContract.getDocumentThumbnail(
plugin.context.contentResolver,
@@ -51,12 +49,12 @@ internal class DocumentsContractApi(private val plugin: SharedStoragePlugin) :
val data =
mapOf(
- "base64" to base64,
- "uri" to "$uri",
- "width" to bitmap.width,
- "height" to bitmap.height,
- "byteCount" to bitmap.byteCount,
- "density" to bitmap.density
+ "base64" to base64,
+ "uri" to "$uri",
+ "width" to bitmap.width,
+ "height" to bitmap.height,
+ "byteCount" to bitmap.byteCount,
+ "density" to bitmap.density
)
launch(Dispatchers.Main) { result.success(data) }
@@ -66,42 +64,6 @@ internal class DocumentsContractApi(private val plugin: SharedStoragePlugin) :
result.notSupported(call.method, API_21)
}
}
- BUILD_DOCUMENT_URI_USING_TREE -> {
- val treeUri = Uri.parse(call.argument("treeUri"))
- val documentId = call.argument("documentId")
-
- if (Build.VERSION.SDK_INT >= API_21) {
- val documentUri = DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId)
-
- result.success("$documentUri")
- } else {
- result.notImplemented()
- }
- }
- BUILD_DOCUMENT_URI -> {
- val authority = call.argument("authority")
- val documentId = call.argument("documentId")
-
- if (Build.VERSION.SDK_INT >= API_21) {
- val documentUri = DocumentsContract.buildDocumentUri(authority, documentId)
-
- result.success("$documentUri")
- } else {
- result.notSupported(call.method, API_21)
- }
- }
- BUILD_TREE_DOCUMENT_URI -> {
- val authority = call.argument("authority")
- val documentId = call.argument("documentId")
-
- if (Build.VERSION.SDK_INT >= API_21) {
- val treeDocumentUri = DocumentsContract.buildTreeDocumentUri(authority, documentId)
-
- result.success("$treeDocumentUri")
- } else {
- result.notSupported(call.method, API_21)
- }
- }
}
}
diff --git a/android/src/main/kotlin/io/lakscastro/sharedstorage/storageaccessframework/lib/DocumentCommon.kt b/android/src/main/kotlin/io/lakscastro/sharedstorage/storageaccessframework/lib/DocumentCommon.kt
index 8e49462..d072e48 100644
--- a/android/src/main/kotlin/io/lakscastro/sharedstorage/storageaccessframework/lib/DocumentCommon.kt
+++ b/android/src/main/kotlin/io/lakscastro/sharedstorage/storageaccessframework/lib/DocumentCommon.kt
@@ -15,34 +15,6 @@ import io.lakscastro.sharedstorage.plugin.API_24
import java.io.ByteArrayOutputStream
import java.io.Closeable
-/**
- * Helper class to make more easy to handle callbacks using Kotlin syntax
- */
-data class CallbackHandler(
- var onSuccess: (T.() -> Unit)? = null,
- var onEnd: (() -> Unit)? = null
-)
-
-/**
- * Generate the `DocumentFile` reference from string `uri` (Single `DocumentFile`)
- */
-@RequiresApi(API_21)
-fun documentFromSingleUri(context: Context, uri: String): DocumentFile? =
- documentFromSingleUri(context, Uri.parse(uri))
-
-/**
- * Generate the `DocumentFile` reference from string `uri` (Single `DocumentFile`)
- */
-@RequiresApi(API_21)
-fun documentFromSingleUri(context: Context, uri: Uri): DocumentFile? {
- val documentUri = DocumentsContract.buildDocumentUri(
- uri.authority,
- DocumentsContract.getDocumentId(uri)
- )
-
- return DocumentFile.fromSingleUri(context, documentUri)
-}
-
/**
* Generate the `DocumentFile` reference from string `uri`
*/
@@ -65,6 +37,7 @@ fun documentFromUri(
}
}
+
/**
* Standard map encoding of a `DocumentFile` and must be used before returning any `DocumentFile`
* from plugin results, like:
@@ -110,7 +83,6 @@ fun createDocumentFileMap(documentFile: DocumentFile?): Map? {
* ```
*/
fun createCursorRowMap(
- rootUri: Uri,
parentUri: Uri,
uri: Uri,
data: Map,
@@ -132,7 +104,6 @@ fun createCursorRowMap(
"data" to formattedData,
"metadata" to mapOf(
"parentUri" to "$parentUri",
- "rootUri" to "$rootUri",
"isDirectory" to isDirectory,
"uri" to "$uri"
)
@@ -156,18 +127,29 @@ fun closeQuietly(closeable: Closeable?) {
@RequiresApi(API_21)
fun traverseDirectoryEntries(
contentResolver: ContentResolver,
- rootUri: Uri,
+ targetUri: Uri,
columns: Array,
rootOnly: Boolean,
block: (data: Map, isLast: Boolean) -> Unit
): Boolean {
+ val documentId = try {
+ DocumentsContract.getDocumentId(targetUri)
+ } catch(e: IllegalArgumentException) {
+ DocumentsContract.getTreeDocumentId(targetUri)
+ }
+ val treeDocumentId = DocumentsContract.getTreeDocumentId(targetUri)
+
+ val rootUri = DocumentsContract.buildTreeDocumentUri(
+ targetUri.authority,
+ treeDocumentId
+ )
val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(
rootUri,
- DocumentsContract.getTreeDocumentId(rootUri)
+ documentId
)
- /// Keep track of our directory hierarchy
- val dirNodes = mutableListOf>(Pair(rootUri, childrenUri))
+ // Keep track of our directory hierarchy
+ val dirNodes = mutableListOf(Pair(targetUri, childrenUri))
while (dirNodes.isNotEmpty()) {
val (parent, children) = dirNodes.removeAt(0)
@@ -187,7 +169,7 @@ fun traverseDirectoryEntries(
val cursor = contentResolver.query(
children,
projection,
- /// TODO: Add support for `selection`, `selectionArgs` and `sortOrder`
+ // TODO: Add support for `selection`, `selectionArgs` and `sortOrder`
null,
null,
null
@@ -217,7 +199,7 @@ fun traverseDirectoryEntries(
val isDirectory = if (mimeType != null) isDirectory(mimeType) else null
val uri = DocumentsContract.buildDocumentUriUsingTree(
- parent,
+ rootUri,
DocumentsContract.getDocumentId(
DocumentsContract.buildDocumentUri(parent.authority, id)
)
@@ -225,7 +207,7 @@ fun traverseDirectoryEntries(
if (isDirectory == true && !rootOnly) {
val nextChildren =
- DocumentsContract.buildChildDocumentsUriUsingTree(rootUri, id)
+ DocumentsContract.buildChildDocumentsUriUsingTree(targetUri, id)
val nextNode = Pair(uri, nextChildren)
@@ -234,7 +216,6 @@ fun traverseDirectoryEntries(
block(
createCursorRowMap(
- rootUri,
parent,
uri,
data,
@@ -267,7 +248,7 @@ fun bitmapToBase64(bitmap: Bitmap): String {
}
/**
- * Trick to verify if is a tree URi even not in API 26+
+ * Trick to verify if is a tree URI even not in API 26+
*/
fun isTreeUri(uri: Uri): Boolean {
if (Build.VERSION.SDK_INT >= API_24) {
diff --git a/android/src/main/kotlin/io/lakscastro/sharedstorage/storageaccessframework/lib/StorageAccessFrameworkConstant.kt b/android/src/main/kotlin/io/lakscastro/sharedstorage/storageaccessframework/lib/StorageAccessFrameworkConstant.kt
index 05e3c59..d1ed988 100644
--- a/android/src/main/kotlin/io/lakscastro/sharedstorage/storageaccessframework/lib/StorageAccessFrameworkConstant.kt
+++ b/android/src/main/kotlin/io/lakscastro/sharedstorage/storageaccessframework/lib/StorageAccessFrameworkConstant.kt
@@ -3,8 +3,6 @@ package io.lakscastro.sharedstorage.storageaccessframework.lib
/**
* Exceptions
*/
-const val EXCEPTION_PARENT_DOCUMENT_MUST_BE_DIRECTORY =
- "EXCEPTION_PARENT_DOCUMENT_MUST_BE_DIRECTORY"
const val EXCEPTION_MISSING_PERMISSIONS = "EXCEPTION_MISSING_PERMISSIONS"
const val EXCEPTION_CANT_OPEN_DOCUMENT_FILE =
"EXCEPTION_CANT_OPEN_DOCUMENT_FILE"
@@ -18,9 +16,6 @@ const val EXCEPTION_CANT_OPEN_FILE_DUE_SECURITY_POLICY =
const val DOCUMENTS_CONTRACT_EXTRA_INITIAL_URI =
"android.provider.extra.INITIAL_URI"
-const val PLUGIN_FILE_PROVIDER_PACKAGE_NAME =
- "fileprovider.io.lakscastro.sharedstorage.storageaccessframework.lib"
-
/**
* Available DocumentFile Method Channel APIs
*/
@@ -41,16 +36,12 @@ const val FIND_FILE = "findFile"
const val COPY = "copy"
const val LAST_MODIFIED = "lastModified"
const val GET_DOCUMENT_THUMBNAIL = "getDocumentThumbnail"
-const val BUILD_DOCUMENT_URI_USING_TREE = "buildDocumentUriUsingTree"
-const val BUILD_DOCUMENT_URI = "buildDocumentUri"
-const val BUILD_TREE_DOCUMENT_URI = "buildTreeDocumentUri"
const val CHILD = "child"
/**
* Available DocumentFileHelper Method Channel APIs
*/
const val OPEN_DOCUMENT_FILE = "openDocumentFile"
-const val GET_REAL_PATH_FROM_URI = "getRealPathFromUri"
/**
* Available Event Channels APIs
diff --git a/docs/Usage/Storage Access Framework.md b/docs/Usage/Storage Access Framework.md
index 41cd5d7..34f2fdd 100644
--- a/docs/Usage/Storage Access Framework.md
+++ b/docs/Usage/Storage Access Framework.md
@@ -71,7 +71,7 @@ This method list files lazily **over a granted uri:**
> **Note** `DocumentFileColumn.id` is optional. It is required to fetch the file list from native API. So it is enabled regardless if you include this column or not. And this applies only to this API (`listFiles`).
```dart
-/// *Must* be a granted uri from `openDocumentTree`
+/// *Must* be a granted uri from `openDocumentTree`, or a URI representing a child under such a granted uri.
final Uri myGrantedUri = ...
final DocumentFile? documentFileOfMyGrantedUri = await myGrantedUri.toDocumentFile();
@@ -321,56 +321,6 @@ const List columns = [
final Stream onNewFileLoaded = documentFileOfMyGrantedUri.listFiles(columns);
```
-### buildDocumentUriUsingTree
-
-Mirror of [`DocumentsContract.buildDocumentUriUsingTree`]()
-
-This is typically used to access documents under a user-selected directory tree, since it doesn't require the user to separately confirm each new document access.
-
-For details refer to the [original docs]().
-
-```dart
-final Uri treeUri = ...
-
-final PartialDocumentFile partialDocumentFile = ...
-
-final String documentId = partialFile.data![DocumentFileColumn.id]!;
-
-final Uri? documentUri = await buildDocumentUriUsingTree(treeUri, documentId);
-```
-
-### buildDocumentUri
-
-Mirror of [`DocumentsContract.buildDocumentUri`]()
-
-Less common method. Use it when you need to (e.g some SAF tutorial/API point to this method or you want a custom authority when building the document file).
-
-For details refer to the [original docs]().
-
-```dart
-final String customAuthority = ...
-final String documentId = ...
-
-final Uri? documentUri = await buildDocumentUri(customAuthority, documentId);
-```
-
-### buildTreeDocumentUri
-
-Mirror of [`DocumentsContract.buildTreeDocumentUri`]()
-
-Less common method. Use it when you need to (e.g some SAF tutorial/API point to this method or you want a custom authority when building the document file).
-
-Build URI representing access to descendant documents of the given [`Document#COLUMN_DOCUMENT_ID`](https://developer.android.com/reference/android/provider/DocumentsContract.Document#COLUMN_DOCUMENT_ID).
-
-For details refer to the [original docs]().
-
-```dart
-final String customAuthority = ...
-final String documentId = ...
-
-final Uri? documentUri = await buildDocumentUri(customAuthority, documentId);
-```
-
### delete
Mirror of [`DocumentFile.delete`]()
diff --git a/docs/index.md b/docs/index.md
index 6b04301..62b25fe 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -79,7 +79,7 @@ These are the brilliant minds behind the development of this plugin!
 www.bibliotecaortodoxa.ro 💻 🐛 🤔 |
 dangilbert 💻 🐛 |
 dhaval-k-simformsolutions 🐛 🤔 |
-  Daniel Dunn 🐛 |
+  Daniel Dunn 🐛 💻 📖 |
diff --git a/example/lib/screens/folder_files/folder_file_card.dart b/example/lib/screens/file_explorer/file_explorer_card.dart
similarity index 85%
rename from example/lib/screens/folder_files/folder_file_card.dart
rename to example/lib/screens/file_explorer/file_explorer_card.dart
index d970b09..874762e 100644
--- a/example/lib/screens/folder_files/folder_file_card.dart
+++ b/example/lib/screens/file_explorer/file_explorer_card.dart
@@ -9,10 +9,10 @@ import '../../theme/spacing.dart';
import '../../widgets/buttons.dart';
import '../../widgets/key_value_text.dart';
import '../../widgets/simple_card.dart';
-import 'folder_file_list.dart';
+import 'file_explorer_page.dart';
-class FolderFileCard extends StatefulWidget {
- const FolderFileCard({
+class FileExplorerCard extends StatefulWidget {
+ const FileExplorerCard({
Key? key,
required this.partialFile,
required this.didUpdateDocument,
@@ -22,10 +22,10 @@ class FolderFileCard extends StatefulWidget {
final void Function(PartialDocumentFile?) didUpdateDocument;
@override
- _FolderFileCardState createState() => _FolderFileCardState();
+ _FileExplorerCardState createState() => _FileExplorerCardState();
}
-class _FolderFileCardState extends State {
+class _FileExplorerCardState extends State {
PartialDocumentFile get file => widget.partialFile;
static const _size = Size.square(150);
@@ -33,14 +33,12 @@ class _FolderFileCardState extends State {
Uint8List? imageBytes;
Future _loadThumbnailIfAvailable() async {
- final rootUri = file.metadata?.rootUri;
- final documentId = file.data?[DocumentFileColumn.id] as String?;
+ final uri = file.metadata?.uri;
- if (rootUri == null || documentId == null) return;
+ if (uri == null) return;
final bitmap = await getDocumentThumbnail(
- rootUri: rootUri,
- documentId: documentId,
+ uri: uri,
width: _size.width,
height: _size.height,
);
@@ -60,7 +58,7 @@ class _FolderFileCardState extends State {
}
@override
- void didUpdateWidget(covariant FolderFileCard oldWidget) {
+ void didUpdateWidget(covariant FileExplorerCard oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.partialFile.data?[DocumentFileColumn.id] !=
@@ -78,7 +76,7 @@ class _FolderFileCardState extends State {
void _openFolderFileListPage(Uri uri) {
Navigator.of(context).push(
MaterialPageRoute(
- builder: (context) => FolderFileList(uri: uri),
+ builder: (context) => FileExplorerPage(uri: uri),
),
);
}
@@ -153,7 +151,6 @@ class _FolderFileCardState extends State {
'summary': '${file.data?[DocumentFileColumn.summary]}',
'id': '${file.data?[DocumentFileColumn.id]}',
'parentUri': '${file.metadata?.parentUri}',
- 'rootUri': '${file.metadata?.rootUri}',
'uri': '${file.metadata?.uri}',
},
),
@@ -164,12 +161,9 @@ class _FolderFileCardState extends State {
'Open Directory',
onTap: () async {
if (_isDirectory) {
- final uri = await buildTreeDocumentUri(
- file.metadata!.rootUri!.authority,
- file.data![DocumentFileColumn.id] as String,
+ _openFolderFileListPage(
+ file.metadata!.uri!,
);
-
- _openFolderFileListPage(uri!);
}
},
),
diff --git a/example/lib/screens/folder_files/folder_file_list.dart b/example/lib/screens/file_explorer/file_explorer_page.dart
similarity index 87%
rename from example/lib/screens/folder_files/folder_file_list.dart
rename to example/lib/screens/file_explorer/file_explorer_page.dart
index 292e35f..91fc1e1 100644
--- a/example/lib/screens/folder_files/folder_file_list.dart
+++ b/example/lib/screens/file_explorer/file_explorer_page.dart
@@ -7,18 +7,21 @@ import '../../theme/spacing.dart';
import '../../widgets/buttons.dart';
import '../../widgets/light_text.dart';
import '../../widgets/simple_card.dart';
-import 'folder_file_card.dart';
+import 'file_explorer_card.dart';
-class FolderFileList extends StatefulWidget {
- const FolderFileList({Key? key, required this.uri}) : super(key: key);
+class FileExplorerPage extends StatefulWidget {
+ const FileExplorerPage({
+ Key? key,
+ required this.uri,
+ }) : super(key: key);
final Uri uri;
@override
- _FolderFileListState createState() => _FolderFileListState();
+ _FileExplorerPageState createState() => _FileExplorerPageState();
}
-class _FolderFileListState extends State {
+class _FileExplorerPageState extends State {
List? _files;
late bool _hasPermission;
@@ -88,7 +91,7 @@ class _FolderFileListState extends State {
(context, index) {
final file = _files![index];
- return FolderFileCard(
+ return FileExplorerCard(
partialFile: file,
didUpdateDocument: (document) {
if (document == null) {
@@ -153,18 +156,22 @@ class _FolderFileListState extends State {
return setState(() => _files = []);
}
- final documentUri = await widget.uri.toDocumentFile();
+ final folderUri = widget.uri;
const columns = [
DocumentFileColumn.displayName,
DocumentFileColumn.size,
DocumentFileColumn.lastModified,
- // Optional column (this can't be removed because it's required to list files)
- DocumentFileColumn.id,
DocumentFileColumn.mimeType,
+ // The column below is a optional column
+ // you can wether include or not here and
+ // it will be always available on the results
+ DocumentFileColumn.id,
];
- _listener = documentUri?.listFiles(columns).listen(
+ final fileListStream = listFiles(folderUri, columns: columns);
+
+ _listener = fileListStream.listen(
(file) {
/// Append new files to the current file list
_files = [...?_files, file];
diff --git a/example/lib/screens/persisted_uris/persisted_uri_card.dart b/example/lib/screens/granted_uris/granted_uri_card.dart
similarity index 92%
rename from example/lib/screens/persisted_uris/persisted_uri_card.dart
rename to example/lib/screens/granted_uris/granted_uri_card.dart
index 465b6e1..6887dce 100644
--- a/example/lib/screens/persisted_uris/persisted_uri_card.dart
+++ b/example/lib/screens/granted_uris/granted_uri_card.dart
@@ -7,8 +7,8 @@ import '../../widgets/key_value_text.dart';
import '../../widgets/simple_card.dart';
import '../folder_files/folder_file_list.dart';
-class PersistedUriCard extends StatefulWidget {
- const PersistedUriCard({
+class GrantedUriCard extends StatefulWidget {
+ const GrantedUriCard({
Key? key,
required this.permissionUri,
required this.onChange,
@@ -18,10 +18,10 @@ class PersistedUriCard extends StatefulWidget {
final VoidCallback onChange;
@override
- _PersistedUriCardState createState() => _PersistedUriCardState();
+ _GrantedUriCardState createState() => _GrantedUriCardState();
}
-class _PersistedUriCardState extends State {
+class _GrantedUriCardState extends State {
Future _appendSampleFile(Uri parentUri) async {
/// Create a new file inside the `parentUri`
final documentFile = await parentUri.toDocumentFile();
diff --git a/example/lib/screens/persisted_uris/persisted_uri_list.dart b/example/lib/screens/granted_uris/granted_uris_page.dart
similarity index 89%
rename from example/lib/screens/persisted_uris/persisted_uri_list.dart
rename to example/lib/screens/granted_uris/granted_uris_page.dart
index 76c9547..fa9ea92 100644
--- a/example/lib/screens/persisted_uris/persisted_uri_list.dart
+++ b/example/lib/screens/granted_uris/granted_uris_page.dart
@@ -4,16 +4,16 @@ import 'package:shared_storage/saf.dart';
import '../../theme/spacing.dart';
import '../../widgets/light_text.dart';
-import 'persisted_uri_card.dart';
+import 'granted_uri_card.dart';
-class PersistedUriList extends StatefulWidget {
- const PersistedUriList({Key? key}) : super(key: key);
+class GrantedUrisPage extends StatefulWidget {
+ const GrantedUrisPage({Key? key}) : super(key: key);
@override
- _PersistedUriListState createState() => _PersistedUriListState();
+ _GrantedUrisPageState createState() => _GrantedUrisPageState();
}
-class _PersistedUriListState extends State {
+class _GrantedUrisPageState extends State {
List? persistedPermissionUris;
@override
@@ -81,7 +81,7 @@ class _PersistedUriListState extends State {
_buildNoFolderAllowedYetWarning()
else
for (final permissionUri in persistedPermissionUris!)
- PersistedUriCard(
+ GrantedUriCard(
permissionUri: permissionUri,
onChange: _loadPersistedUriPermissions,
)
diff --git a/lib/src/saf/document_file.dart b/lib/src/saf/document_file.dart
index 3298001..e072d20 100644
--- a/lib/src/saf/document_file.dart
+++ b/lib/src/saf/document_file.dart
@@ -1,8 +1,6 @@
import 'dart:typed_data';
import '../common/functional_extender.dart';
-import 'document_file_column.dart';
-import 'partial_document_file.dart';
import 'saf.dart' as saf;
extension UriDocumentFileUtils on Uri {
@@ -97,10 +95,6 @@ class DocumentFile {
/// {@macro sharedstorage.saf.canWrite}
Future canWrite() async => saf.canWrite(uri);
- /// {@macro sharedstorage.saf.listFiles}
- Stream listFiles(List columns) =>
- saf.listFiles(uri, columns: columns);
-
/// {@macro sharedstorage.saf.exists}
Future exists() => saf.exists(uri);
diff --git a/lib/src/saf/partial_document_file.dart b/lib/src/saf/partial_document_file.dart
index d22662a..0efdc4a 100644
--- a/lib/src/saf/partial_document_file.dart
+++ b/lib/src/saf/partial_document_file.dart
@@ -52,7 +52,6 @@ class PartialDocumentFile {
class QueryMetadata {
const QueryMetadata._({
required this.parentUri,
- required this.rootUri,
required this.isDirectory,
required this.uri,
});
@@ -60,14 +59,12 @@ class QueryMetadata {
factory QueryMetadata.fromMap(Map map) {
return QueryMetadata._(
parentUri: _parseUri(map['parentUri'] as String?),
- rootUri: _parseUri(map['rootUri'] as String?),
isDirectory: map['isDirectory'] as bool?,
uri: _parseUri(map['uri'] as String?),
);
}
final Uri? parentUri;
- final Uri? rootUri;
final bool? isDirectory;
final Uri? uri;
@@ -80,7 +77,6 @@ class QueryMetadata {
Map toMap() {
return {
'parentUri': '$parentUri',
- 'rootUri': '$rootUri',
'isDirectory': isDirectory,
'uri': uri,
};
@@ -91,11 +87,10 @@ class QueryMetadata {
if (other is! QueryMetadata) return false;
return other.parentUri == parentUri &&
- other.rootUri == rootUri &&
other.isDirectory == isDirectory &&
other.uri == uri;
}
@override
- int get hashCode => Object.hash(parentUri, rootUri, isDirectory, uri);
+ int get hashCode => Object.hash(parentUri, isDirectory, uri);
}
diff --git a/lib/src/saf/saf.dart b/lib/src/saf/saf.dart
index a63ecad..3b79293 100644
--- a/lib/src/saf/saf.dart
+++ b/lib/src/saf/saf.dart
@@ -102,14 +102,12 @@ Future canWrite(Uri uri) async => kDocumentFileChannel
/// [Refer to details](https://developer.android.com/reference/android/provider/DocumentsContract#getDocumentThumbnail(android.content.ContentResolver,%20android.net.Uri,%20android.graphics.Point,%20android.os.CancellationSignal)).
/// {@endtemplate}
Future getDocumentThumbnail({
- required Uri rootUri,
- required String documentId,
+ required Uri uri,
required double width,
required double height,
}) async {
final args = {
- 'rootUri': '$rootUri',
- 'documentId': documentId,
+ 'uri': '$uri',
'width': width,
'height': height,
};
@@ -173,63 +171,6 @@ Stream listFiles(
Future exists(Uri uri) async => kDocumentFileChannel
.invokeMethod('exists', {'uri': '$uri'});
-/// {@template sharedstorage.saf.buildDocumentUriUsingTree}
-/// Equivalent to `DocumentsContract.buildDocumentUriUsingTree`.
-///
-/// [Refer to details](https://developer.android.com/reference/android/provider/DocumentsContract#buildDocumentUriUsingTree%28android.net.Uri,%20java.lang.String%29).
-/// {@endtemplate}
-Future buildDocumentUriUsingTree(Uri treeUri, String documentId) async {
- final args = {
- 'treeUri': '$treeUri',
- 'documentId': documentId,
- };
-
- final uri = await kDocumentsContractChannel.invokeMethod(
- 'buildDocumentUriUsingTree',
- args,
- );
-
- return uri?.apply((u) => Uri.parse(u));
-}
-
-/// {@template sharedstorage.saf.buildDocumentUri}
-/// Equivalent to `DocumentsContract.buildDocumentUri`.
-///
-/// [Refer to details](https://developer.android.com/reference/android/provider/DocumentsContract#buildDocumentUri%28java.lang.String,%20java.lang.String%29).
-/// {@endtemplate}
-Future buildDocumentUri(String authority, String documentId) async {
- final args = {
- 'authority': authority,
- 'documentId': documentId,
- };
-
- final uri = await kDocumentsContractChannel.invokeMethod(
- 'buildDocumentUri',
- args,
- );
-
- return uri?.apply((u) => Uri.parse(u));
-}
-
-/// {@template sharedstorage.saf.buildDocumentUri}
-/// Equivalent to `DocumentsContract.buildDocumentUri`.
-///
-/// [Refer to details](https://developer.android.com/reference/android/provider/DocumentsContract#buildDocumentUri%28java.lang.String,%20java.lang.String%29).
-/// {@endtemplate}
-Future buildTreeDocumentUri(String authority, String documentId) async {
- final args = {
- 'authority': authority,
- 'documentId': documentId,
- };
-
- final uri = await kDocumentsContractChannel.invokeMethod(
- 'buildTreeDocumentUri',
- args,
- );
-
- return uri?.apply((u) => Uri.parse(u));
-}
-
/// {@template sharedstorage.saf.delete}
/// Equivalent to `DocumentFile.delete`.
///
diff --git a/pubspec.yaml b/pubspec.yaml
index ccb99ff..4ab815f 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
name: shared_storage
description: "Flutter plugin to get Android shared folders like DCIM, Downloads, Video, Audio. Works with Android 4.1 (API Level 16+)"
-version: 0.3.1
+version: 0.4.0
homepage: https://github.com/lakscastro/shared-storage
repository: https://github.com/lakscastro/shared-storage
issue_tracker: https://github.com/lakscastro/shared-storage/issues