From 5a12548633746ccf1864f2fff17c9a3d89e4751a Mon Sep 17 00:00:00 2001 From: PytatoDuck <179164236+PytatoDuck@users.noreply.github.com> Date: Tue, 14 Oct 2025 04:12:20 +0500 Subject: [PATCH 1/3] Use `${applicationId}` for FileProvider authority This makes the authority dynamic and avoids potential conflicts in different build variants or when the application ID is changed. --- app/src/main/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4f2ecd3d..6a5d855d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -167,7 +167,7 @@ Date: Tue, 14 Oct 2025 04:13:05 +0500 Subject: [PATCH 2/3] Configure debug build type Adds a "debug" build type to the Gradle configuration - Disables minification. - Appends " | Debug" to the version name. - Appends ".debug" to the application ID. --- app/build.gradle.kts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 148d6d81..1aa9355c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -23,6 +23,11 @@ android { } buildTypes { + debug { + isMinifyEnabled = false + versionNameSuffix = " | Debug" + applicationIdSuffix = ".debug" + } release { isMinifyEnabled = true proguardFiles( From d2e54adb1985bbc7f258006a5f4710a34ffc36bd Mon Sep 17 00:00:00 2001 From: PytatoDuck <179164236+PytatoDuck@users.noreply.github.com> Date: Tue, 14 Oct 2025 04:15:01 +0500 Subject: [PATCH 3/3] Notify MediaStore of file changes Inform Android's MediaStore about file creation, deletion, and renaming to ensure other apps (like Galleries) reflect these changes promptly. - Add `MediaStoreUtils` to scan modified files. - Update `CopyTask`, `DeleteTask`, and `RenameTask` to track file changes and trigger a media scan upon completion. --- .../file/explorer/common/MediaStoreUtils.kt | 44 +++++++++++++++++++ .../screen/main/tab/files/task/CopyTask.kt | 27 +++++++++++- .../screen/main/tab/files/task/DeleteTask.kt | 16 +++++++ .../screen/main/tab/files/task/RenameTask.kt | 19 ++++++++ 4 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/com/raival/compose/file/explorer/common/MediaStoreUtils.kt diff --git a/app/src/main/java/com/raival/compose/file/explorer/common/MediaStoreUtils.kt b/app/src/main/java/com/raival/compose/file/explorer/common/MediaStoreUtils.kt new file mode 100644 index 00000000..bc8826d0 --- /dev/null +++ b/app/src/main/java/com/raival/compose/file/explorer/common/MediaStoreUtils.kt @@ -0,0 +1,44 @@ +package com.raival.compose.file.explorer.common + +import android.content.Context +import android.media.MediaScannerConnection +import java.io.File + +object MediaStoreUtils { + /** + * Notifies the MediaStore that one or more files have changed. + * + * @param context Context to use (applicationContext recommended) + * @param files List of files to notify + * @param mimeTypes Optional MIME types (should be same length as files array, or null) + */ + @JvmOverloads + fun notifyFileChanged( + context: Context, + files: List, + mimeTypes: List? = null + ) { + MediaScannerConnection.scanFile( + context, + files.map { it.absolutePath }.toTypedArray(), + mimeTypes?.toTypedArray(), + null + ) + } + + /** + * Notifies the MediaStore that a single file has changed. + * + * @param context Context to use (applicationContext recommended) + * @param file The file that has changed. + * @param mimeType Optional MIME type of the file. + */ + @JvmOverloads + fun notifyFileChanged( + context: Context, + file: File, + mimeType: String? = null + ) { + notifyFileChanged(context, listOf(file), mimeTypes = mimeType?.let { listOf(it) }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/task/CopyTask.kt b/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/task/CopyTask.kt index 3b6a9ece..4106983d 100644 --- a/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/task/CopyTask.kt +++ b/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/task/CopyTask.kt @@ -3,6 +3,7 @@ package com.raival.compose.file.explorer.screen.main.tab.files.task import com.raival.compose.file.explorer.App.Companion.globalClass import com.raival.compose.file.explorer.App.Companion.logger import com.raival.compose.file.explorer.R +import com.raival.compose.file.explorer.common.MediaStoreUtils import com.raival.compose.file.explorer.common.emptyString import com.raival.compose.file.explorer.common.isNot import com.raival.compose.file.explorer.common.listFilesAndEmptyDirs @@ -27,10 +28,15 @@ class CopyTask( val sourceFiles: List, val deleteSourceFiles: Boolean ) : Task() { + private val context = globalClass.applicationContext + private var parameters: CopyTaskParameters? = null private val pendingFiles: ArrayList = arrayListOf() private var canPerformAtomicFileMove = true + private val modifiedFiles = mutableListOf() + private val deletedFiles = mutableListOf() + override val metadata = createTaskMetadata() override val progressMonitor = TaskProgressMonitor( status = TaskStatus.PENDING, @@ -220,6 +226,8 @@ class CopyTask( append("\n") } } + + notifyMediaStoreChanges() } } @@ -313,7 +321,9 @@ class CopyTask( progress = (index + 1f) / successfulItems.size } - (item.content as LocalFileHolder).file.deleteRecursively() + val file = (item.content as LocalFileHolder).file + trackFileDeletion(file) + file.deleteRecursively() } // Clean up empty directories @@ -464,6 +474,7 @@ class CopyTask( } else { source.copyTo(destination, overwrite) } + trackFileModification(destination) } else { destination.mkdirs() || destination.exists() } @@ -649,6 +660,7 @@ class CopyTask( input.copyTo(output) } } + trackFileModification(destinationFile) } else { destinationFile.mkdirs() } @@ -816,4 +828,17 @@ class CopyTask( else -> emptyList() } } + + private fun trackFileModification(file: File) { + if (!modifiedFiles.contains(file)) modifiedFiles.add(file) + } + + private fun trackFileDeletion(file: File) { + if (!deletedFiles.contains(file)) deletedFiles.add(file) + } + + private fun notifyMediaStoreChanges() { + if (modifiedFiles.isNotEmpty()) MediaStoreUtils.notifyFileChanged(context, modifiedFiles) + if (deletedFiles.isNotEmpty()) MediaStoreUtils.notifyFileChanged(context, deletedFiles) + } } \ No newline at end of file diff --git a/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/task/DeleteTask.kt b/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/task/DeleteTask.kt index f2cee4f7..654af316 100644 --- a/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/task/DeleteTask.kt +++ b/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/task/DeleteTask.kt @@ -3,6 +3,7 @@ package com.raival.compose.file.explorer.screen.main.tab.files.task import com.raival.compose.file.explorer.App.Companion.globalClass import com.raival.compose.file.explorer.App.Companion.logger import com.raival.compose.file.explorer.R +import com.raival.compose.file.explorer.common.MediaStoreUtils import com.raival.compose.file.explorer.common.emptyString import com.raival.compose.file.explorer.common.toFormattedDate import com.raival.compose.file.explorer.screen.main.tab.files.holder.ContentHolder @@ -19,9 +20,13 @@ import java.util.zip.ZipOutputStream class DeleteTask( val sourceContent: List ) : Task() { + private val context = globalClass.applicationContext + private var parameters: DeleteTaskParameters? = null private var pendingContent = arrayListOf() + private val deletedFiles = mutableListOf() + override val metadata = System.currentTimeMillis().toFormattedDate().let { time -> TaskMetadata( id = id, @@ -174,6 +179,7 @@ class DeleteTask( try { val localFile = itemToDelete.source as LocalFileHolder + trackFileDeletion(localFile.file) if (localFile.file.deleteRecursively()) { itemToDelete.status = TaskContentStatus.SUCCESS } else { @@ -191,6 +197,8 @@ class DeleteTask( } } } + + notifyMediaStoreChanges() } private suspend fun handleZipFileDeletion() { @@ -360,4 +368,12 @@ class DeleteTask( val source: ContentHolder, var status: TaskContentStatus ) + + private fun trackFileDeletion(file: File) { + if (!deletedFiles.contains(file)) deletedFiles.add(file) + } + + private fun notifyMediaStoreChanges() { + if (deletedFiles.isNotEmpty()) MediaStoreUtils.notifyFileChanged(context, deletedFiles) + } } \ No newline at end of file diff --git a/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/task/RenameTask.kt b/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/task/RenameTask.kt index 622ed22c..7e01683c 100644 --- a/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/task/RenameTask.kt +++ b/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/task/RenameTask.kt @@ -3,6 +3,7 @@ package com.raival.compose.file.explorer.screen.main.tab.files.task import com.raival.compose.file.explorer.App.Companion.globalClass import com.raival.compose.file.explorer.App.Companion.logger import com.raival.compose.file.explorer.R +import com.raival.compose.file.explorer.common.MediaStoreUtils import com.raival.compose.file.explorer.common.emptyString import com.raival.compose.file.explorer.common.toFormattedDate import com.raival.compose.file.explorer.screen.main.tab.files.holder.ContentHolder @@ -20,9 +21,13 @@ import java.util.zip.ZipFile import java.util.zip.ZipOutputStream class RenameTask(val sourceContent: List) : Task() { + private val context = globalClass.applicationContext + private var parameters: RenameTaskParameters? = null private var pendingContent = arrayListOf() + private val modifiedFiles = mutableListOf() + override val metadata = System.currentTimeMillis().toFormattedDate().let { time -> TaskMetadata( id = id, @@ -171,6 +176,8 @@ class RenameTask(val sourceContent: List) : Task() { } if (progressMonitor.status == TaskStatus.RUNNING) { + if (sampleContent is LocalFileHolder) notifyMediaStoreChanges() + progressMonitor.apply { status = TaskStatus.SUCCESS progress = 1.0f @@ -201,6 +208,7 @@ class RenameTask(val sourceContent: List) : Task() { val newFile = File(itemToRename.newPath) if (localFile.file.renameTo(newFile)) { + trackFileModification(localFile.file, newFile) itemToRename.status = TaskContentStatus.SUCCESS } else { throw Exception(globalClass.getString(R.string.failed_to_rename_file)) @@ -217,6 +225,8 @@ class RenameTask(val sourceContent: List) : Task() { } } } + + notifyMediaStoreChanges() } private suspend fun handleZipFileRenaming() { @@ -502,4 +512,13 @@ class RenameTask(val sourceContent: List) : Task() { val newPath: String, var status: TaskContentStatus ) + + private fun trackFileModification(oldFile: File, newFile: File) { + if (!modifiedFiles.contains(oldFile)) modifiedFiles.add(oldFile) + if (!modifiedFiles.contains(newFile)) modifiedFiles.add(newFile) + } + + private fun notifyMediaStoreChanges() { + if (modifiedFiles.isNotEmpty()) MediaStoreUtils.notifyFileChanged(context, modifiedFiles) + } } \ No newline at end of file