diff --git a/README.md b/README.md index c601f5d..6552edd 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,22 @@ # 如何使用 +## 初始化 + +``` +//这个是存储的帮助类 +private lateinit var xHelper:StorageXHelper +//saf帮助类 +private lateinit var helper: SafHelper.Helper +//mediastore的帮助类 +private lateinit var mediaStoreHelper: MediaStoreHelper.Helper + +//初始化 +xHelper = StoreX.with(this) +helper=xHelper.safHelper +mdiaStoreHelper = xHelper.mediaStoreHelper +``` + ## SAF部分 1. 在`FragmentActivity`或`Fragment`中初始化一个`helper`对象。(不一定非要在`onCreate`之类的地方初始化) @@ -14,7 +30,7 @@ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) - val helper: SAFHelper.Helper = StoreX.initSaf(this) + val helper: SAFHelper.Helper = StoreX.with(this).safHelper } ``` @@ -29,12 +45,6 @@ helper.requestOneFolder {uri-> //被用户选择的目录uri //保存目录的读写权限 helper.savePerms(uri) -//删除一个文件 -helper.deleteFile(fileUri) {b-> - if (b) { - makeToast("文件删除成功") - } -} //写文件 helper.writeFile(testFileUri) { outputStream -> @@ -65,8 +75,10 @@ helper.selectFile { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) - val mediaStoreHelper: MediaStoreHelper.Helper = StoreX.initMediaStore(this) - } + val mediaStoreHelper: MediaStoreHelper.Helper = + StoreX.with(this).mediaStoreHelper + +} ``` 2. 调用`mediaStoreHelper`中的方法,例如: diff --git a/app/src/main/java/com/kiylx/simplestore/MainActivity.kt b/app/src/main/java/com/kiylx/simplestore/MainActivity.kt index 98876b0..416ec11 100644 --- a/app/src/main/java/com/kiylx/simplestore/MainActivity.kt +++ b/app/src/main/java/com/kiylx/simplestore/MainActivity.kt @@ -19,14 +19,16 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import androidx.documentfile.provider.DocumentFile import com.google.android.material.button.MaterialButton +import com.kiylx.store_lib.MediaStoreHelper import com.kiylx.store_lib.StoreX import com.kiylx.store_lib.kit.MimeTypeConsts import com.kiylx.store_lib.kit.ext.filter import com.kiylx.store_lib.kit.ext.makeToast import com.kiylx.store_lib.kit.ext.writeFileFromUri import com.kiylx.store_lib.mediastore.FileLocate -import com.kiylx.store_lib.mediastore.MediaStoreHelper -import com.kiylx.store_lib.saf.SAFHelper +import com.kiylx.store_lib.StorageXHelper +import com.kiylx.store_lib.SafHelper +import com.kiylx.store_lib.documentfile.deleteFile import java.nio.charset.StandardCharsets import java.util.concurrent.TimeUnit import kotlin.concurrent.thread @@ -40,16 +42,20 @@ import kotlin.concurrent.thread // 2、如果你的 app 卸载后再重装的话系统不会认为是同一个 app(也就是你卸载之前创建的文件,再次安装 app 后必须先申请 READ_EXTERNAL_STORAGE 权限后才能获取到) class MainActivity : AppCompatActivity(), View.OnClickListener { - private lateinit var helper: SAFHelper.Helper + private lateinit var xHelper:StorageXHelper + private lateinit var helper: SafHelper.Helper private lateinit var mediaStoreHelper: MediaStoreHelper.Helper + private val hander = Handler(Looper.getMainLooper()) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) - helper = StoreX.initSaf(this) - mediaStoreHelper = StoreX.initMediaStore(this) + xHelper = StoreX.with(this) + helper=xHelper.safHelper + mediaStoreHelper = xHelper.mediaStoreHelper + requestPermission() findViewById(R.id.request_prem).setOnClickListener(this) findViewById(R.id.save_prem).setOnClickListener(this) @@ -116,7 +122,7 @@ class MainActivity : AppCompatActivity(), View.OnClickListener { if (testFileUri == Uri.EMPTY) { makeToast("没有文件可删除") } else - helper.deleteFile(testFileUri) { + deleteFile(testFileUri) { if (it) { testFileUri = Uri.EMPTY makeToast("文件删除成功") diff --git a/store_lib/src/main/java/com/kiylx/store_lib/StorageXHelper.kt b/store_lib/src/main/java/com/kiylx/store_lib/StorageXHelper.kt new file mode 100644 index 0000000..447224b --- /dev/null +++ b/store_lib/src/main/java/com/kiylx/store_lib/StorageXHelper.kt @@ -0,0 +1,138 @@ +package com.kiylx.store_lib + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.fragment.app.FragmentManager +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.LifecycleOwner +import com.kiylx.store_lib.mediastore.MediaStoreFragment +import com.kiylx.store_lib.mediastore.MediaStoreMethod +import com.kiylx.store_lib.saf.FileMethod +import com.kiylx.store_lib.saf.SafImplFragment + +class StorageXHelper : LifecycleEventObserver { + private var activity: FragmentActivity + set(value) { + field = value + value.lifecycle.addObserver(this) + } + private var fragment: Fragment? = null + + constructor(fragment: Fragment) { + this.fragment = fragment + this.activity = fragment.requireActivity() + } + + constructor(fragmentActivity: FragmentActivity) { + this.activity = fragmentActivity + } + + /** + * 在Activity中获取 FragmentManager,如果在Fragment中,则获取 ChildFragmentManager。 + */ + private val fragmentManager: FragmentManager + get() { + return fragment?.childFragmentManager ?: activity.supportFragmentManager + } + + /** + * 使用此实例操作文件 + */ + val safHelper: SafHelper.Helper by lazy { + SafHelper(fragmentManager).helper + } + + val mediaStoreHelper: MediaStoreHelper.Helper by lazy { + MediaStoreHelper(fragmentManager).helper + } + + companion object { + /** + * TAG of InvisibleFragment to find and create. + */ + internal const val SAF_FRAGMENT_TAG = "SafInvisibleFragment" + + /** + * MediaStore Fragment tag + */ + internal const val MS_FRAGMENT_TAG = "MediaStoreInvisibleFragment" + + } + + override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { + if (event == Lifecycle.Event.ON_DESTROY) { + val t = fragmentManager.beginTransaction() + val existed1 = fragmentManager.findFragmentByTag(SAF_FRAGMENT_TAG) + val existed2 = fragmentManager.findFragmentByTag(MS_FRAGMENT_TAG) + if (existed2 != null) { + t.remove(existed2) + } + if (existed1 != null) { + t.remove(existed1) + } + t.commitAllowingStateLoss() + } + } +} + +class SafHelper(private val fragmentManager: FragmentManager) { + private val invisibleFragment: SafImplFragment + get() { + val existed = fragmentManager.findFragmentByTag(StorageXHelper.SAF_FRAGMENT_TAG) + if (existed != null) { + return existed as SafImplFragment + } else { + val invisibleFragment = SafImplFragment() + fragmentManager.beginTransaction() + .add(invisibleFragment, StorageXHelper.SAF_FRAGMENT_TAG) + .commitNowAllowingStateLoss() + return invisibleFragment + } + } + + /** + * 使用此实例操作文件 + */ + val helper: Helper by lazy { + Helper(invisibleFragment) + } + + /** + * 所有的文件操作,全部委托给[invisibleFragment] + * 使用委托的方式,隐藏掉fragment,只向外界提供接口的实现 + */ + inner class Helper(private val invisibleFragment: SafImplFragment) : + FileMethod by invisibleFragment +} + +class MediaStoreHelper(private val fragmentManager: FragmentManager) { + private val invisibleFragment: MediaStoreFragment + get() { + val existed = fragmentManager.findFragmentByTag(StorageXHelper.MS_FRAGMENT_TAG) + if (existed != null) { + return existed as MediaStoreFragment + } else { + val invisibleFragment = MediaStoreFragment() + fragmentManager.beginTransaction() + .add(invisibleFragment, StorageXHelper.MS_FRAGMENT_TAG) + .commitNowAllowingStateLoss() + return invisibleFragment + } + } + + /** + * 使用此实例操作文件 + */ + val helper: Helper by lazy { + Helper(invisibleFragment) + } + + /** + * 所有的文件操作,全部委托给[invisibleFragment] + * 使用委托的方式,隐藏掉fragment,只向外界提供接口的实现 + */ + inner class Helper(private val invisibleFragment: MediaStoreFragment) : + MediaStoreMethod by invisibleFragment + +} diff --git a/store_lib/src/main/java/com/kiylx/store_lib/StoreX.kt b/store_lib/src/main/java/com/kiylx/store_lib/StoreX.kt index 6a06599..179b546 100644 --- a/store_lib/src/main/java/com/kiylx/store_lib/StoreX.kt +++ b/store_lib/src/main/java/com/kiylx/store_lib/StoreX.kt @@ -4,29 +4,28 @@ package com.kiylx.store_lib import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity -import com.kiylx.store_lib.mediastore.MediaStoreHelper -import com.kiylx.store_lib.saf.SAFHelper class StoreX { companion object { @JvmStatic - fun initSaf(fragmentActivity: FragmentActivity): SAFHelper.Helper { - return SAFHelper(fragmentActivity).helper + fun with(fragmentActivity: FragmentActivity): StorageXHelper { + return StorageXHelper(fragmentActivity) } @JvmStatic - fun initSaf(fragment: Fragment): SAFHelper.Helper { - return SAFHelper(fragment).helper + fun with(fragment: Fragment): StorageXHelper { + return StorageXHelper(fragment) } @JvmStatic - fun initMediaStore(fragmentActivity: FragmentActivity): MediaStoreHelper.Helper { - return MediaStoreHelper(fragmentActivity).helper + fun StorageXHelper.safHelper(): SafHelper.Helper { + return this.safHelper } @JvmStatic - fun initMediaStore(fragment: Fragment): MediaStoreHelper.Helper { - return MediaStoreHelper(fragment).helper + fun StorageXHelper.mediaStoreHelper(): MediaStoreHelper.Helper { + return this.mediaStoreHelper } + } } \ No newline at end of file diff --git a/store_lib/src/main/java/com/kiylx/store_lib/documentfile/DocumentFileHelper.kt b/store_lib/src/main/java/com/kiylx/store_lib/documentfile/DocumentFileHelper.kt new file mode 100644 index 0000000..e713cd7 --- /dev/null +++ b/store_lib/src/main/java/com/kiylx/store_lib/documentfile/DocumentFileHelper.kt @@ -0,0 +1,198 @@ +package com.kiylx.store_lib.documentfile + +import android.content.Intent +import android.net.Uri +import android.provider.DocumentsContract +import android.provider.DocumentsProvider +import androidx.annotation.RequiresApi +import androidx.documentfile.provider.DocumentFile +import androidx.fragment.app.FragmentActivity +import com.kiylx.store_lib.kit.ext.runSafely +import com.kiylx.store_lib.kit.ext.runSafelyNoNull +import com.kiylx.store_lib.kit.ext.runSafelyNullable +import com.kiylx.store_lib.kit.fileProcessResult +import com.kiylx.store_lib.kit.uriResult + + +/** + * @param parentUri 要创建文件的父文件夹uri + * @param displayName 要创建的文件的名称 + * @param mimeType 文件类型[com.kiylx.store_lib.kit.MimeTypeConsts] + * @param block(documentFile) 创建文件后给与documentFile,或者创建失败将会抛出异常 + */ +fun FragmentActivity.createDocumentFile( + parentUri: Uri, + displayName: String, + mimeType: String, + block: (file: DocumentFile?) -> Unit, +) { + val df: DocumentFile? = + DocumentFile.fromTreeUri(this, parentUri) + val documentFile = df?.createFile(mimeType, displayName) + documentFile.runSafelyNullable(block) +} + +/** + * @param parentUri 要创建文件文件夹的父文件夹uri + * @param displayName 要创建的文件夹的名称 + * @param block(documentFile) 创建文件夹后给与documentFile,或者创建失败给与null + */ +fun FragmentActivity.createDocumentFileDir( + parentUri: Uri, + displayName: String, + block: (file: DocumentFile?) -> Unit, +) { + val df = DocumentFile.fromTreeUri(this, parentUri) + val document = df?.createDirectory(displayName) + document.runSafelyNullable(block) +} + + +/** + * @param parentUri 要创建文件的父文件夹uri + * @param displayName 要创建的文件的名称 + * @param mimeType 文件类型[com.kiylx.store_lib.kit.MimeTypeConsts] + * @param block(uri) 创建文件后给与uri,或者创建失败将会抛出异常 + */ +fun FragmentActivity.createFile( + parentUri: Uri, + displayName: String, + mimeType: String, + block: (uri: Uri?) -> Unit, +) { + val uri = DocumentsContract.createDocument(contentResolver, parentUri, mimeType, displayName) + uri.runSafelyNullable(block) +} + +/** + * 复制文档 + * + * @param sourceFileUri document with + * [android.provider.DocumentsContract.Document.FLAG_SUPPORTS_COPY] + * @param targetFolderUri document which will become a parent of the source + * document's copy. + * @param block(uri) 被复制文档的uri,如果失败,内部会抛出异常 + */ +@RequiresApi(24) +fun FragmentActivity.copyFile( + sourceFileUri: Uri, + targetFolderUri: Uri, + block: uriResult,/* = (uri: android.net.Uri?) -> kotlin.Unit */ +) { + val uri: Uri? = + DocumentsContract.copyDocument( + contentResolver, + sourceFileUri, + targetFolderUri + ) + uri.runSafelyNullable(block) +} + +/** + * Moves the given document under a new parent. + * + * @param sourceFileUri document with + * [android.provider.DocumentsContract.Document.FLAG_SUPPORTS_MOVE] + * @param sourceFileParentUri parent document of the document to move. + * @param targetFolderUri document which will become a new parent of the + * source document. + * @param block(uri) uri is the moved document, or {@code null} if failed. + */ +@RequiresApi(24) +fun FragmentActivity.moveFile( + sourceFileUri: Uri, + sourceFileParentUri: Uri, + targetFolderUri: Uri, + block: uriResult,/* = (uri: android.net.Uri?) -> kotlin.Unit */ +) { + val uri: Uri? = + DocumentsContract.moveDocument( + contentResolver, + sourceFileUri, + sourceFileParentUri, + targetFolderUri + ) + uri.runSafelyNullable(block) +} + +/** + * Change the display name of an existing document. + * + * If the underlying provider needs to create a new + * [android.provider.DocumentsContract.Document.COLUMN_DOCUMENT_ID] to + * represent the updated display name, that new document is returned and + * the original document is no longer valid. Otherwise, the original + * document is returned. + * + * @param sourceFileUri document with + * [android.provider.DocumentsContract.Document.FLAG_SUPPORTS_RENAME] + * @param newName updated name for document + * @param block(uri) the existing or new document after the rename, or + * {@code null} if failed. + */ +fun FragmentActivity.rename( + sourceFileUri: Uri, + newName: String, + block: uriResult,/* = (uri: android.net.Uri) -> kotlin.Unit */ +) { + DocumentsContract.renameDocument( + contentResolver, + sourceFileUri, + newName + ).runSafelyNullable(block) +} + +/** + * Removes the given document from a parent directory. + * + * In contrast to [deleteFile] it requires specifying the parent. This + * method is especially useful if the document can be in multiple parents. + * + * @param sourceFileUri document with + * [android.provider.DocumentsContract.Document.FLAG_SUPPORTS_REMOVE] + * @param sourceFileParentUri parent document of the document to remove. + * @param block(b) true if the document was removed successfully. + */ +@RequiresApi(24) +fun FragmentActivity.removeFile( + sourceFileUri: Uri, + sourceFileParentUri: Uri, + block: fileProcessResult, /* = (result: kotlin.Boolean) -> kotlin.Unit */ +) { + DocumentsContract.removeDocument( + contentResolver, + sourceFileUri, sourceFileParentUri + ).runSafely(block) +} + +fun FragmentActivity.deleteFile(uri: Uri, block: fileProcessResult) { + DocumentsContract.deleteDocument(getContentResolver(), uri) + .runSafely(block) +} + +/** + * Create a [DocumentFile] representing the document tree rooted + * at the given [Uri]. This is only useful on devices running + * [android.os.Build.VERSION_CODES.LOLLIPOP] or later, and will + * return {@code null} when called on earlier platform versions. + * + * @param treeUri the [Intent.getData] from a successful + * [Intent.ACTION_OPEN_DOCUMENT_TREE] request. + */ +fun FragmentActivity.getDocumentTreeFile(treeUri: Uri): DocumentFile? { + return DocumentFile.fromTreeUri(this, treeUri) +} + +/** + * Create a [DocumentFile] representing the single document + * at the given [Uri]. This is only useful on devices running + * [android.os.Build.VERSION_CODES.KITKAT] or later, and will + * return {@code null} when called on earlier platform versions. + * + * @param singleUri the [Intent.getData] from a successful + * [Intent.ACTION_OPEN_DOCUMENT] or [Intent.ACTION_CREATE_DOCUMENT] + * request. + */ +fun FragmentActivity.getSingleDocumentFile(singleUri: Uri): DocumentFile? { + return DocumentFile.fromSingleUri(this, singleUri) +} \ No newline at end of file diff --git a/store_lib/src/main/java/com/kiylx/store_lib/mediastore/MediaStoreFragment.kt b/store_lib/src/main/java/com/kiylx/store_lib/mediastore/MediaStoreFragment.kt index 16f2855..f8739a3 100644 --- a/store_lib/src/main/java/com/kiylx/store_lib/mediastore/MediaStoreFragment.kt +++ b/store_lib/src/main/java/com/kiylx/store_lib/mediastore/MediaStoreFragment.kt @@ -10,8 +10,8 @@ import android.provider.MediaStore import androidx.annotation.RequiresApi import androidx.annotation.WorkerThread import androidx.fragment.app.Fragment -import com.kiylx.store_lib.kit.ext.runSafely import com.kiylx.store_lib.kit.noNullUriResult +import java.io.File class MediaStoreFragment : Fragment(), MediaStoreMethod { /** @@ -26,7 +26,7 @@ class MediaStoreFragment : Fragment(), MediaStoreMethod { block: noNullUriResult, ) { if ((relativePath.isNotEmpty())) { - genPic(name, "${Environment.DIRECTORY_DCIM}/$relativePath", mime, block) + genPic(name, "${Environment.DIRECTORY_DCIM}${File.separator}$relativePath", mime, block) } else { genPic(name, Environment.DIRECTORY_DCIM, mime, block) } @@ -40,14 +40,14 @@ class MediaStoreFragment : Fragment(), MediaStoreMethod { block: noNullUriResult, ) { if ((relativePath.isNotEmpty())) { - genPic(name, "${Environment.DIRECTORY_PICTURES}/$relativePath", mime, block) + genPic(name, "${Environment.DIRECTORY_PICTURES}${File.separator}$relativePath", mime, block) } else { genPic(name, Environment.DIRECTORY_PICTURES, mime, block) } } /** - * @param relativePath 相对图片文件夹的相对路径 + * @param path 相对图片文件夹的相对路径 * 例如 传入test,会产生 storage/emulated/0/Pictures/test/ */ @RequiresApi(Build.VERSION_CODES.Q) @@ -76,12 +76,12 @@ class MediaStoreFragment : Fragment(), MediaStoreMethod { @RequiresApi(Build.VERSION_CODES.Q) override fun newDownloadFile( name: String, - path: String, + relativePath: String, mime: String, block: noNullUriResult, ) { - if (path.isNotEmpty()) { - genDownloadFile(name, Environment.DIRECTORY_DOWNLOADS + path, mime, block) + if (relativePath.isNotEmpty()) { + genDownloadFile(name, "${Environment.DIRECTORY_DOWNLOADS}${File.separator}$relativePath", mime, block) } else { genDownloadFile(name, Environment.DIRECTORY_DOWNLOADS, mime, block) } @@ -113,12 +113,12 @@ class MediaStoreFragment : Fragment(), MediaStoreMethod { @RequiresApi(Build.VERSION_CODES.Q) override fun newMovieFile( name: String, - path: String, + relativePath: String, mime: String, block: noNullUriResult, ) { - if (path.isNotEmpty()) { - genMovieFile(name, Environment.DIRECTORY_MOVIES + path, mime, block) + if (relativePath.isNotEmpty()) { + genMovieFile(name, "${Environment.DIRECTORY_MOVIES}${File.separator}$relativePath", mime, block) } else { genMovieFile(name, Environment.DIRECTORY_MOVIES, mime, block) } @@ -150,12 +150,12 @@ class MediaStoreFragment : Fragment(), MediaStoreMethod { @RequiresApi(Build.VERSION_CODES.Q) override fun newMusicFile( name: String, - path: String, + relativePath: String, mime: String, block: noNullUriResult, ) { - if (path.isNotEmpty()) { - genMusicFile(name, Environment.DIRECTORY_MUSIC + path, mime, block) + if (relativePath.isNotEmpty()) { + genMusicFile(name, "${Environment.DIRECTORY_MUSIC}${File.separator}$relativePath", mime, block) } else { genMusicFile(name, Environment.DIRECTORY_MUSIC, mime, block) } @@ -287,13 +287,15 @@ class MediaStoreFragment : Fragment(), MediaStoreMethod { } /** - * 表示几个公共文件夹的位置 + * Environment下的字段,表示几个公共文件夹的位置 * * IMAGE:Environment.DIRECTORY_PICTURES * DCIM:Environment.DIRECTORY_DCIM * VIDEO:Environment.DIRECTORY_MOVIES * AUDIO:Environment.DIRECTORY_MUSIC * DOWNLOAD:Environment.DIRECTORY_DOWNLOADS + * + * MediaStore下的EXTERNAL_CONTENT_URI,表示数据库的字段 */ enum class FileLocate(val uri: Uri) { IMAGE(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { diff --git a/store_lib/src/main/java/com/kiylx/store_lib/mediastore/MediaStoreHelper.kt b/store_lib/src/main/java/com/kiylx/store_lib/mediastore/MediaStoreHelper.kt deleted file mode 100644 index 3146cae..0000000 --- a/store_lib/src/main/java/com/kiylx/store_lib/mediastore/MediaStoreHelper.kt +++ /dev/null @@ -1,62 +0,0 @@ -package com.kiylx.store_lib.mediastore - -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentActivity -import androidx.fragment.app.FragmentManager - -class MediaStoreHelper { - private var activity: FragmentActivity - private var fragment: Fragment? = null - - constructor(fragment: Fragment) { - this.fragment = fragment - this.activity = fragment.requireActivity() - } - - constructor(fragmentActivity: FragmentActivity) { - this.activity = fragmentActivity - } - - /** - * 在Activity中获取 FragmentManager,如果在Fragment中,则获取 ChildFragmentManager。 - */ - private val fragmentManager: FragmentManager - get() { - return fragment?.childFragmentManager ?: activity.supportFragmentManager - } - - private val invisibleFragment: MediaStoreFragment - get() { - val existed = fragmentManager.findFragmentByTag(FRAGMENT_TAG) - if (existed != null) { - return existed as MediaStoreFragment - } else { - val invisibleFragment = MediaStoreFragment() - fragmentManager.beginTransaction() - .add(invisibleFragment, FRAGMENT_TAG) - .commitNowAllowingStateLoss() - return invisibleFragment - } - } - - /** - * 使用此实例操作文件 - */ - val helper: Helper by lazy { - Helper(invisibleFragment) - } - - /** - * 所有的文件操作,全部委托给[invisibleFragment] - * 使用委托的方式,隐藏掉fragment,只向外界提供接口的实现 - */ - inner class Helper(private val invisibleFragment: MediaStoreFragment) : - MediaStoreMethod by invisibleFragment - - companion object { - /** - * TAG of InvisibleFragment to find and create. - */ - private const val FRAGMENT_TAG = "MediaStoreInvisibleFragment" - } -} diff --git a/store_lib/src/main/java/com/kiylx/store_lib/mediastore/MediaStoreMethod.kt b/store_lib/src/main/java/com/kiylx/store_lib/mediastore/MediaStoreMethod.kt index 1949a30..d55804d 100644 --- a/store_lib/src/main/java/com/kiylx/store_lib/mediastore/MediaStoreMethod.kt +++ b/store_lib/src/main/java/com/kiylx/store_lib/mediastore/MediaStoreMethod.kt @@ -48,7 +48,7 @@ interface MediaStoreMethod { @RequiresApi(Build.VERSION_CODES.Q) fun newDownloadFile( name: String, - path: String = "", + relativePath: String = "", mime: String, block: noNullUriResult, ) @@ -56,7 +56,7 @@ interface MediaStoreMethod { @RequiresApi(Build.VERSION_CODES.Q) fun newMovieFile( name: String, - path: String = "", + relativePath: String = "", mime: String, block: noNullUriResult, ) @@ -64,7 +64,7 @@ interface MediaStoreMethod { @RequiresApi(Build.VERSION_CODES.Q) fun newMusicFile( name: String, - path: String = "", + relativePath: String = "", mime: String, block: noNullUriResult, ) diff --git a/store_lib/src/main/java/com/kiylx/store_lib/saf/FileMethod.kt b/store_lib/src/main/java/com/kiylx/store_lib/saf/FileMethod.kt index 523ab38..206c816 100644 --- a/store_lib/src/main/java/com/kiylx/store_lib/saf/FileMethod.kt +++ b/store_lib/src/main/java/com/kiylx/store_lib/saf/FileMethod.kt @@ -2,9 +2,9 @@ package com.kiylx.store_lib.saf import android.content.ContentResolver import android.net.Uri -import androidx.annotation.RequiresApi -import androidx.documentfile.provider.DocumentFile -import com.kiylx.store_lib.kit.* +import com.kiylx.store_lib.kit.input +import com.kiylx.store_lib.kit.noNullUriResult +import com.kiylx.store_lib.kit.output interface FileMethod { /** @@ -47,53 +47,6 @@ interface FileMethod { block: noNullUriResult, ) - - fun deleteFile( - uri: Uri, - block: fileProcessResult, - ) - - fun copyFile(sourceFileUri: Uri, targetFolderUri: Uri, block: uriResult) - - - fun moveFile( - sourceFileUri: Uri, - sourceFileParentUri: Uri, - targetFolderUri: Uri, - block: uriResult, - ) - - - fun rename(sourceFileUri: Uri, newName: String, block: uriResult) - - @RequiresApi(24) - fun removeFile(sourceFileUri: Uri, sourceFileParentUri: Uri, block: fileProcessResult) - - - - /** - * @param parentUri 要创建文件的父文件夹uri - * @param displayName 要创建的文件的名称 - * @param mimeType 文件类型[com.kiylx.store_lib.kit.MimeTypeConsts] - * @param block(documentFile) 创建文件后给与documentFile,或者创建失败给与null - */ - fun createDocumentFile( - parentUri: Uri, - displayName: String, - mimeType: String, - block: (file: DocumentFile?) -> Unit, - ) - - /** - * @param parentUri 要创建文件文件夹的父文件夹uri - * @param displayName 要创建的文件夹的名称 - * @param block(documentFile) 创建文件夹后给与documentFile,或者创建失败给与null - */ - fun createDir( - parentUri: Uri, - displayName: String, - block: (file: DocumentFile?) -> Unit, - ) fun readFile( uri: Uri, input: input, @@ -103,29 +56,7 @@ interface FileMethod { uri: Uri, output: output, ) - fun getContentResolver(): ContentResolver - - /** - * Create a {@link DocumentFile} representing the document tree rooted at - * the given {@link Uri}. This is only useful on devices running - * {@link android.os.Build.VERSION_CODES#LOLLIPOP} or later, and will return - * {@code null} when called on earlier platform versions. - * - * @param treeUri the {@link Intent#getData()} from a successful - * {@link Intent#ACTION_OPEN_DOCUMENT_TREE} request. - */ - fun getDocumentTreeFile(treeUri: Uri): DocumentFile? - /** - * Create a {@link DocumentFile} representing the single document at the - * given {@link Uri}. This is only useful on devices running - * {@link android.os.Build.VERSION_CODES#KITKAT} or later, and will return - * {@code null} when called on earlier platform versions. - * - * @param singleUri the {@link Intent#getData()} from a successful - * {@link Intent#ACTION_OPEN_DOCUMENT} or - * {@link Intent#ACTION_CREATE_DOCUMENT} request. - */ - fun getSingleDocumentFile(singleUri: Uri): DocumentFile? + fun getContentResolver(): ContentResolver } \ No newline at end of file diff --git a/store_lib/src/main/java/com/kiylx/store_lib/saf/SAFHelper.kt b/store_lib/src/main/java/com/kiylx/store_lib/saf/SAFHelper.kt deleted file mode 100644 index 0010cf8..0000000 --- a/store_lib/src/main/java/com/kiylx/store_lib/saf/SAFHelper.kt +++ /dev/null @@ -1,62 +0,0 @@ -package com.kiylx.store_lib.saf - -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentActivity -import androidx.fragment.app.FragmentManager - -class SAFHelper { - private var activity: FragmentActivity - private var fragment: Fragment? = null - - constructor(fragment: Fragment) { - this.fragment = fragment - this.activity = fragment.requireActivity() - } - - constructor(fragmentActivity: FragmentActivity) { - this.activity = fragmentActivity - } - - /** - * 在Activity中获取 FragmentManager,如果在Fragment中,则获取 ChildFragmentManager。 - */ - private val fragmentManager: FragmentManager - get() { - return fragment?.childFragmentManager ?: activity.supportFragmentManager - } - - private val invisibleFragment: SafImplFragment - get() { - val existed = fragmentManager.findFragmentByTag(FRAGMENT_TAG) - if (existed != null) { - return existed as SafImplFragment - } else { - val invisibleFragment = SafImplFragment() - fragmentManager.beginTransaction() - .add(invisibleFragment, FRAGMENT_TAG) - .commitNowAllowingStateLoss() - return invisibleFragment - } - } - - /** - * 使用此实例操作文件 - */ - val helper: Helper by lazy { - Helper(invisibleFragment) - } - - /** - * 所有的文件操作,全部委托给[invisibleFragment] - * 使用委托的方式,隐藏掉fragment,只向外界提供接口的实现 - */ - inner class Helper(private val invisibleFragment: SafImplFragment) : - FileMethod by invisibleFragment - - companion object { - /** - * TAG of InvisibleFragment to find and create. - */ - private const val FRAGMENT_TAG = "SafInvisibleFragment" - } -} diff --git a/store_lib/src/main/java/com/kiylx/store_lib/saf/SafImplFragment.kt b/store_lib/src/main/java/com/kiylx/store_lib/saf/SafImplFragment.kt index dc518d3..279ccbf 100644 --- a/store_lib/src/main/java/com/kiylx/store_lib/saf/SafImplFragment.kt +++ b/store_lib/src/main/java/com/kiylx/store_lib/saf/SafImplFragment.kt @@ -1,18 +1,18 @@ package com.kiylx.store_lib.saf -import android.app.Activity.RESULT_OK import android.content.ContentResolver import android.content.Intent import android.net.Uri import android.os.Build import android.provider.DocumentsContract -import androidx.activity.result.ActivityResult -import androidx.annotation.RequiresApi -import androidx.documentfile.provider.DocumentFile -import androidx.documentfile.provider.DocumentFile.fromTreeUri import androidx.fragment.app.Fragment -import com.kiylx.store_lib.kit.* -import com.kiylx.store_lib.kit.ext.* +import com.kiylx.store_lib.kit.ext.readFileFromUri +import com.kiylx.store_lib.kit.ext.runSafelyNoNull +import com.kiylx.store_lib.kit.ext.startActivityForResult +import com.kiylx.store_lib.kit.ext.writeFileFromUri +import com.kiylx.store_lib.kit.input +import com.kiylx.store_lib.kit.noNullUriResult +import com.kiylx.store_lib.kit.output /** 从 MediaStore接口或者SAF获取到文件Uri后,请利用Uri打开FD 或者输入输出流,而不要转换成文件路径去访问。 */ class SafImplFragment : Fragment(), FileMethod { @@ -101,146 +101,6 @@ class SafImplFragment : Fragment(), FileMethod { } } - /** - * @param parentUri 要创建文件的父文件夹uri - * @param displayName 要创建的文件的名称 - * @param mimeType 文件类型[com.kiylx.store_lib.kit.MimeTypeConsts] - * @param block(documentFile) 创建文件后给与documentFile,或者创建失败将会抛出异常 - */ - override fun createDocumentFile( - parentUri: Uri, - displayName: String, - mimeType: String, - block: (file: DocumentFile?) -> Unit, - ) { - val df: DocumentFile? = - fromTreeUri(requireActivity(), parentUri) - val documentFile = df?.createFile(mimeType, displayName) - documentFile.runSafelyNullable(block) - } - - /** - * @param parentUri 要创建文件文件夹的父文件夹uri - * @param displayName 要创建的文件夹的名称 - * @param block(documentFile) 创建文件夹后给与documentFile,或者创建失败给与null - */ - override fun createDir( - parentUri: Uri, - displayName: String, - block: (file: DocumentFile?) -> Unit, - ) { - val df = fromTreeUri(requireActivity(), parentUri) - val document = df?.createDirectory(displayName) - document.runSafelyNullable(block) - } - - /** - * 复制文档 - * - * @param sourceFileUri document with - * [android.provider.DocumentsContract.Document.FLAG_SUPPORTS_COPY] - * @param targetFolderUri document which will become a parent of the source - * document's copy. - * @param block(uri) 被复制文档的uri,如果失败,内部会抛出异常 - */ - @RequiresApi(24) - override fun copyFile( - sourceFileUri: Uri, - targetFolderUri: Uri, - block: uriResult,/* = (uri: android.net.Uri?) -> kotlin.Unit */ - ) { - val uri: Uri? = - DocumentsContract.copyDocument( - getContentResolver(), - sourceFileUri, - targetFolderUri - ) - uri.runSafelyNullable(block) - } - - /** - * Moves the given document under a new parent. - * - * @param sourceFileUri document with - * [android.provider.DocumentsContract.Document.FLAG_SUPPORTS_MOVE] - * @param sourceFileParentUri parent document of the document to move. - * @param targetFolderUri document which will become a new parent of the - * source document. - * @param block(uri) uri is the moved document, or {@code null} if failed. - */ - @RequiresApi(24) - override fun moveFile( - sourceFileUri: Uri, - sourceFileParentUri: Uri, - targetFolderUri: Uri, - block: uriResult,/* = (uri: android.net.Uri?) -> kotlin.Unit */ - ) { - val uri: Uri? = - DocumentsContract.moveDocument( - getContentResolver(), - sourceFileUri, - sourceFileParentUri, - targetFolderUri - ) - uri.runSafelyNullable(block) - } - - /** - * Change the display name of an existing document. - * - * If the underlying provider needs to create a new - * [android.provider.DocumentsContract.Document.COLUMN_DOCUMENT_ID] to - * represent the updated display name, that new document is returned and - * the original document is no longer valid. Otherwise, the original - * document is returned. - * - * @param sourceFileUri document with - * [android.provider.DocumentsContract.Document.FLAG_SUPPORTS_RENAME] - * @param newName updated name for document - * @param block(uri) the existing or new document after the rename, or - * {@code null} if failed. - */ - override fun rename( - sourceFileUri: Uri, - newName: String, - block: uriResult,/* = (uri: android.net.Uri) -> kotlin.Unit */ - ) { - DocumentsContract.renameDocument( - getContentResolver(), - sourceFileUri, - newName - ).runSafelyNullable(block) - } - - /** - * Removes the given document from a parent directory. - * - * In contrast to [deleteFile] it requires specifying the parent. This - * method is especially useful if the document can be in multiple parents. - * - * @param sourceFileUri document with - * [android.provider.DocumentsContract.Document.FLAG_SUPPORTS_REMOVE] - * @param sourceFileParentUri parent document of the document to remove. - * @param block(b) true if the document was removed successfully. - */ - @RequiresApi(24) - override fun removeFile( - sourceFileUri: Uri, - sourceFileParentUri: Uri, - block: fileProcessResult, /* = (result: kotlin.Boolean) -> kotlin.Unit */ - ) { - DocumentsContract.removeDocument( - getContentResolver(), - sourceFileUri, sourceFileParentUri - ).runSafely(block) - } - - override fun deleteFile(uri: Uri, block: fileProcessResult) { - DocumentsContract.deleteDocument(getContentResolver(), uri) - .runSafely(block) - } - - override fun readFile(uri: Uri, input: input) { requireActivity().readFileFromUri(uri, input) } @@ -250,31 +110,4 @@ class SafImplFragment : Fragment(), FileMethod { } override fun getContentResolver(): ContentResolver = requireActivity().contentResolver - - /** - * Create a [DocumentFile] representing the document tree rooted - * at the given [Uri]. This is only useful on devices running - * [android.os.Build.VERSION_CODES.LOLLIPOP] or later, and will - * return {@code null} when called on earlier platform versions. - * - * @param treeUri the [Intent.getData] from a successful - * [Intent.ACTION_OPEN_DOCUMENT_TREE] request. - */ - override fun getDocumentTreeFile(treeUri: Uri): DocumentFile? { - return fromTreeUri(requireActivity(), treeUri) - } - - /** - * Create a [DocumentFile] representing the single document - * at the given [Uri]. This is only useful on devices running - * [android.os.Build.VERSION_CODES.KITKAT] or later, and will - * return {@code null} when called on earlier platform versions. - * - * @param singleUri the [Intent.getData] from a successful - * [Intent.ACTION_OPEN_DOCUMENT] or [Intent.ACTION_CREATE_DOCUMENT] - * request. - */ - override fun getSingleDocumentFile(singleUri: Uri): DocumentFile? { - return DocumentFile.fromSingleUri(requireActivity(), singleUri) - } }