From eb7f61fbc851c532d71113116bd16b030960ca18 Mon Sep 17 00:00:00 2001 From: Binondi Date: Tue, 3 Jun 2025 15:09:19 +0530 Subject: [PATCH] Changes For Folder Feature --- .../calculator/activities/HiddenActivity.kt | 5 +- .../calculator/activities/PreviewActivity.kt | 4 +- .../activities/ViewFolderActivity.kt | 60 +++++++++++------ .../org/calculator/adapters/FileAdapter.kt | 67 +++++-------------- .../calculator/adapters/FileDiffCallback.kt | 7 +- .../devs/org/calculator/utils/FileManager.kt | 23 ++++--- app/src/main/res/drawable/ic_delete.xml | 2 +- app/src/main/res/drawable/ic_edit.xml | 2 +- app/src/main/res/drawable/ic_list.xml | 2 +- app/src/main/res/drawable/ic_more.xml | 4 +- app/src/main/res/drawable/ic_settings.xml | 2 +- app/src/main/res/layout/activity_hidden.xml | 17 +++-- .../main/res/layout/activity_view_folder.xml | 9 +-- app/src/main/res/layout/item_folder.xml | 2 + .../res/layout/item_folder_list_style.xml | 2 + 15 files changed, 105 insertions(+), 103 deletions(-) diff --git a/app/src/main/java/devs/org/calculator/activities/HiddenActivity.kt b/app/src/main/java/devs/org/calculator/activities/HiddenActivity.kt index 4c789f6..924bd7b 100644 --- a/app/src/main/java/devs/org/calculator/activities/HiddenActivity.kt +++ b/app/src/main/java/devs/org/calculator/activities/HiddenActivity.kt @@ -47,7 +47,6 @@ class HiddenActivity : AppCompatActivity() { binding = ActivityHiddenBinding.inflate(layoutInflater) setContentView(binding.root) - // Initialize managers fileManager = FileManager(this, this) folderManager = FolderManager(this) dialogUtil = DialogUtil(this) @@ -56,16 +55,16 @@ class HiddenActivity : AppCompatActivity() { setupClickListeners() setupBackPressedHandler() - // Initialize permissions and load data fileManager.askPermission(this) - // Set initial orientation icon based on saved preference refreshCurrentView() } private fun setupInitialUIState() { binding.addFolder.visibility = View.VISIBLE + binding.settings.visibility = View.VISIBLE + binding.folderOrientation.visibility = View.VISIBLE binding.deleteSelected.visibility = View.GONE binding.delete.visibility = View.GONE binding.menuButton.visibility = View.GONE diff --git a/app/src/main/java/devs/org/calculator/activities/PreviewActivity.kt b/app/src/main/java/devs/org/calculator/activities/PreviewActivity.kt index 8eb7809..b386ee5 100644 --- a/app/src/main/java/devs/org/calculator/activities/PreviewActivity.kt +++ b/app/src/main/java/devs/org/calculator/activities/PreviewActivity.kt @@ -125,7 +125,7 @@ class PreviewActivity : AppCompatActivity() { private fun clickListeners() { binding.delete.setOnClickListener { - val fileUri = FileManager.FileManager().getContentUriImage(this, files[binding.viewPager.currentItem], filetype) + val fileUri = FileManager.FileManager().getContentUriImage(this, files[binding.viewPager.currentItem]) if (fileUri != null) { dialogUtil.showMaterialDialog( getString(R.string.delete_file), @@ -153,7 +153,7 @@ class PreviewActivity : AppCompatActivity() { } binding.unHide.setOnClickListener { - val fileUri = FileManager.FileManager().getContentUriImage(this, files[binding.viewPager.currentItem], filetype) + val fileUri = FileManager.FileManager().getContentUriImage(this, files[binding.viewPager.currentItem]) if (fileUri != null) { dialogUtil.showMaterialDialog( getString(R.string.un_hide_file), diff --git a/app/src/main/java/devs/org/calculator/activities/ViewFolderActivity.kt b/app/src/main/java/devs/org/calculator/activities/ViewFolderActivity.kt index 03b6590..a901f14 100644 --- a/app/src/main/java/devs/org/calculator/activities/ViewFolderActivity.kt +++ b/app/src/main/java/devs/org/calculator/activities/ViewFolderActivity.kt @@ -27,7 +27,6 @@ import devs.org.calculator.utils.DialogUtil import devs.org.calculator.utils.FileManager import devs.org.calculator.utils.FileManager.Companion.HIDDEN_DIR import devs.org.calculator.utils.FolderManager -import devs.org.calculator.utils.PrefsUtil import kotlinx.coroutines.launch import java.io.File @@ -52,7 +51,7 @@ class ViewFolderActivity : AppCompatActivity() { private var customDialog: androidx.appcompat.app.AlertDialog? = null private var dialogShowTime: Long = 0 - private val MINIMUM_DIALOG_DURATION = 1700L + private val MINIMUM_DIALOG_DURATION = 1200L override fun onCreate(savedInstanceState: Bundle?) { @@ -110,6 +109,7 @@ class ViewFolderActivity : AppCompatActivity() { } } + @SuppressLint("SetTextI18n") private fun showCustomDialog(count: Int) { val dialogView = ProccessingDialogBinding.inflate(layoutInflater) customDialog = MaterialAlertDialogBuilder(this) @@ -129,16 +129,26 @@ class ViewFolderActivity : AppCompatActivity() { mainHandler.postDelayed({ customDialog?.dismiss() customDialog = null + updateFilesToAdapter() }, remainingTime) } else { customDialog?.dismiss() customDialog = null + updateFilesToAdapter() } } + private fun updateFilesToAdapter() { + openFolder(currentFolder!!) + } + private fun processSelectedFiles(uriList: List) { val targetFolder = currentFolder ?: hiddenDir + if (!targetFolder.exists()) { + targetFolder.mkdirs() + File(targetFolder, ".nomedia").createNewFile() + } showCustomDialog(uriList.size) lifecycleScope.launch { @@ -147,8 +157,9 @@ class ViewFolderActivity : AppCompatActivity() { object : FileProcessCallback { override fun onFilesProcessedSuccessfully(copiedFiles: List) { mainHandler.post { - refreshCurrentView() - dismissCustomDialog() + mainHandler.postDelayed({ + dismissCustomDialog() + }, 1000) } } @@ -188,6 +199,11 @@ class ViewFolderActivity : AppCompatActivity() { } private fun openFolder(folder: File) { + // Ensure folder exists and has .nomedia file + if (!folder.exists()) { + folder.mkdirs() + File(folder, ".nomedia").createNewFile() + } val files = folderManager.getFilesInFolder(folder) binding.folderName.text = folder.name @@ -212,7 +228,7 @@ class ViewFolderActivity : AppCompatActivity() { fileAdapter = FileAdapter(this, this, folder, prefs.getBoolean("showFileName", true), onFolderLongClick = { isSelected -> - handleFileSelectionModeChange(isSelected, 0) + handleFileSelectionModeChange(isSelected) }).apply { setFileOperationCallback(object : FileAdapter.FileOperationCallback { override fun onFileDeleted(file: File) { @@ -228,7 +244,7 @@ class ViewFolderActivity : AppCompatActivity() { } override fun onSelectionModeChanged(isSelectionMode: Boolean, selectedCount: Int) { - handleFileSelectionModeChange(isSelectionMode, selectedCount) + handleFileSelectionModeChange(isSelectionMode) } override fun onSelectionCountChanged(selectedCount: Int) { @@ -243,18 +259,16 @@ class ViewFolderActivity : AppCompatActivity() { binding.recyclerView.visibility = View.VISIBLE binding.noItems.visibility = View.GONE - // Setup menu button click listener binding.menuButton.setOnClickListener { fileAdapter?.let { adapter -> showFileOptionsMenu(adapter.getSelectedItems()) } } - - // Set initial UI state showFileViewIcons() } + @Deprecated("This method has been deprecated in favor of using the\n {@link OnBackPressedDispatcher} via {@link #getOnBackPressedDispatcher()}.\n The OnBackPressedDispatcher controls how back button events are dispatched\n to one or more {@link OnBackPressedCallback} objects.") @SuppressLint("MissingSuperCall") override fun onBackPressed() { handleBackPress() @@ -268,7 +282,7 @@ class ViewFolderActivity : AppCompatActivity() { super.onBackPressed() } - private fun handleFileSelectionModeChange(isSelectionMode: Boolean, selectedCount: Int) { + private fun handleFileSelectionModeChange(isSelectionMode: Boolean) { if (isSelectionMode) { showFileSelectionIcons() } else { @@ -277,7 +291,6 @@ class ViewFolderActivity : AppCompatActivity() { } private fun updateSelectionCountDisplay(selectedCount: Int) { - // Update any UI elements that show selection count if (selectedCount > 0) { showFileSelectionIcons() } else { @@ -366,13 +379,10 @@ class ViewFolderActivity : AppCompatActivity() { private fun refreshCurrentFolder() { currentFolder?.let { folder -> val files = folderManager.getFilesInFolder(folder) - fileAdapter?.submitList(files) - - if (files.isEmpty()) { - showEmptyState() - } else { + if (files.isNotEmpty()) { binding.recyclerView.visibility = View.VISIBLE binding.noItems.visibility = View.GONE + fileAdapter?.submitList(files.toMutableList()) fileAdapter?.let { adapter -> if (adapter.isInSelectionMode()) { showFileSelectionIcons() @@ -380,6 +390,8 @@ class ViewFolderActivity : AppCompatActivity() { showFileViewIcons() } } + } else { + showEmptyState() } } } @@ -414,6 +426,7 @@ class ViewFolderActivity : AppCompatActivity() { addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) } + closeFabs() pickImageLauncher.launch(intent) } @@ -481,14 +494,17 @@ class ViewFolderActivity : AppCompatActivity() { var allUnhidden = true selectedFiles.forEach { file -> try { - val fileUri = Uri.fromFile(file) - val result = fileManager.copyFileToNormalDir(fileUri) - if (result == null) { - allUnhidden = false + val fileUri = FileManager.FileManager().getContentUriImage(this@ViewFolderActivity, file) + + if (fileUri != null) { + val result = fileManager.copyFileToNormalDir(fileUri) + if (result == null) { + allUnhidden = false + } } else { - // Delete the hidden file after successful copy - file.delete() + allUnhidden = false } + } catch (e: Exception) { allUnhidden = false } diff --git a/app/src/main/java/devs/org/calculator/adapters/FileAdapter.kt b/app/src/main/java/devs/org/calculator/adapters/FileAdapter.kt index 0d3c2a5..a1d7729 100644 --- a/app/src/main/java/devs/org/calculator/adapters/FileAdapter.kt +++ b/app/src/main/java/devs/org/calculator/adapters/FileAdapter.kt @@ -472,7 +472,7 @@ class FileAdapter( override fun onBindViewHolder(holder: FileViewHolder, position: Int, payloads: MutableList) { if (payloads.isEmpty()) { - super.onBindViewHolder(holder, position, payloads) + onBindViewHolder(holder, position) } else { if (position < itemCount) { val file = getItem(position) @@ -481,9 +481,18 @@ class FileAdapter( } } - /** - * Enter selection mode and notify callback immediately - */ + override fun submitList(list: List?) { + val currentList = currentList.toMutableList() + if (list == null) { + currentList.clear() + super.submitList(null) + } else { + // Create a new list to force update + val newList = list.toMutableList() + super.submitList(newList) + } + } + fun enterSelectionMode() { if (!isSelectionMode) { isSelectionMode = true @@ -491,9 +500,6 @@ class FileAdapter( } } - /** - * Exit selection mode and clear all selections - */ fun exitSelectionMode() { if (isSelectionMode) { isSelectionMode = false @@ -503,9 +509,6 @@ class FileAdapter( } } - /** - * Clear selection without exiting selection mode - */ fun clearSelection() { if (selectedItems.isNotEmpty()) { val previouslySelected = selectedItems.toSet() @@ -519,9 +522,6 @@ class FileAdapter( } } - /** - * Select all items - */ fun selectAll() { if (!isSelectionMode) { enterSelectionMode() @@ -542,9 +542,6 @@ class FileAdapter( updateSelectionItems(selectedItems.toSet(), previouslySelected) } - /** - * Efficiently update selection UI for changed items only - */ private fun updateSelectionItems(newSelections: Set, oldSelections: Set) { val changedItems = (oldSelections - newSelections) + (newSelections - oldSelections) changedItems.forEach { position -> @@ -554,36 +551,24 @@ class FileAdapter( } } - /** - * Centralized method to notify selection mode changes - */ private fun notifySelectionModeChange() { fileOperationCallback?.get()?.onSelectionModeChanged(isSelectionMode, selectedItems.size) onFolderLongClick(isSelectionMode) } - /** - * Get selected files - */ fun getSelectedItems(): List { return selectedItems.mapNotNull { position -> if (position < itemCount) getItem(position) else null } } - /** - * Get selected file count - */ + fun getSelectedCount(): Int = selectedItems.size - /** - * Check if in selection mode - */ + fun isInSelectionMode(): Boolean = isSelectionMode - /** - * Delete selected files with proper error handling and background processing - */ + fun deleteSelectedFiles() { val selectedFiles = getSelectedItems() if (selectedFiles.isEmpty()) return @@ -647,9 +632,7 @@ class FileAdapter( } } - /** - * Share selected files - */ + fun shareSelectedFiles() { val selectedFiles = getSelectedItems() if (selectedFiles.isEmpty()) return @@ -707,10 +690,7 @@ class FileAdapter( exitSelectionMode() } - /** - * Handle back press - exit selection mode if active - * @return true if selection mode was active and has been exited, false otherwise - */ + fun onBackPressed(): Boolean { return if (isSelectionMode) { exitSelectionMode() @@ -720,10 +700,6 @@ class FileAdapter( } } - /** - * Force refresh of all selection states - * Call this if you notice selection UI issues - */ fun refreshSelectionStates() { if (isSelectionMode) { selectedItems.forEach { position -> @@ -736,9 +712,6 @@ class FileAdapter( } } - /** - * Clean up resources to prevent memory leaks - */ fun cleanup() { try { if (!fileExecutor.isShutdown) { @@ -751,10 +724,6 @@ class FileAdapter( fileOperationCallback?.clear() fileOperationCallback = null } - - /** - * Call this from the activity's onDestroy() - */ override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { super.onDetachedFromRecyclerView(recyclerView) cleanup() diff --git a/app/src/main/java/devs/org/calculator/adapters/FileDiffCallback.kt b/app/src/main/java/devs/org/calculator/adapters/FileDiffCallback.kt index 1058113..7446983 100644 --- a/app/src/main/java/devs/org/calculator/adapters/FileDiffCallback.kt +++ b/app/src/main/java/devs/org/calculator/adapters/FileDiffCallback.kt @@ -17,7 +17,8 @@ class FileDiffCallback : DiffUtil.ItemCallback() { oldItem.length() == newItem.length() && oldItem.lastModified() == newItem.lastModified() && oldItem.canRead() == newItem.canRead() && - oldItem.canWrite() == newItem.canWrite() + oldItem.canWrite() == newItem.canWrite() && + oldItem.exists() == newItem.exists() } override fun getChangePayload(oldItem: File, newItem: File): Any? { @@ -37,6 +38,10 @@ class FileDiffCallback : DiffUtil.ItemCallback() { changes.add("MODIFIED_DATE_CHANGED") } + if (oldItem.exists() != newItem.exists()) { + changes.add("EXISTENCE_CHANGED") + } + return if (changes.isNotEmpty()) changes else null } } \ No newline at end of file diff --git a/app/src/main/java/devs/org/calculator/utils/FileManager.kt b/app/src/main/java/devs/org/calculator/utils/FileManager.kt index 296da70..d071a3d 100644 --- a/app/src/main/java/devs/org/calculator/utils/FileManager.kt +++ b/app/src/main/java/devs/org/calculator/utils/FileManager.kt @@ -20,6 +20,7 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import devs.org.calculator.callbacks.FileProcessCallback import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.File @@ -79,6 +80,12 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life // Get the target directory (i am using the current opened folder as target folder) val targetDir = folderName + + // Ensure target directory exists and has .nomedia file + if (!targetDir.exists()) { + targetDir.mkdirs() + File(targetDir, ".nomedia").createNewFile() + } // Create target file val mimeType = contentResolver.getType(uri) @@ -219,11 +226,7 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life if (documentFile?.exists() == true && documentFile.canWrite()) { val deleted = documentFile.delete() withContext(Dispatchers.Main) { - if (deleted) { // Toast.makeText(context, "File deleted successfully", Toast.LENGTH_SHORT).show() - } else { - Toast.makeText(context, "Failed to hide/unhide file", Toast.LENGTH_SHORT).show() - } } return@withContext } @@ -232,7 +235,7 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life try { context.contentResolver.delete(photoUri, null, null) withContext(Dispatchers.Main) { -// Toast.makeText(context, "File deleted successfully", Toast.LENGTH_SHORT).show() +// Toast.makeText(context, "File deleted successfully", Toast.LENGTH_SHORT).show() } } catch (e: SecurityException) { // Handle security exception for Android 10 and above @@ -290,8 +293,8 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life } } - class FileManager(){ - fun getContentUriImage(context: Context, file: File, fileType: FileType): Uri? { + class FileManager{ + fun getContentUriImage(context: Context, file: File): Uri? { // Query MediaStore for the file val projection = arrayOf(MediaStore.MediaColumns._ID) @@ -341,7 +344,7 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life suspend fun processMultipleFiles( uriList: List, - fileType: File, + targetFolder: File, callback: FileProcessCallback, currentDir: File? = null ) { @@ -349,12 +352,14 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life val copiedFiles = mutableListOf() for (uri in uriList) { try { - val file = copyFileToHiddenDir(uri, fileType, currentDir) + val file = copyFileToHiddenDir(uri, targetFolder, currentDir) file?.let { copiedFiles.add(it) } } catch (e: Exception) { e.printStackTrace() } } + delay(500) + withContext(Dispatchers.Main) { if (copiedFiles.isNotEmpty()) { callback.onFilesProcessedSuccessfully(copiedFiles) diff --git a/app/src/main/res/drawable/ic_delete.xml b/app/src/main/res/drawable/ic_delete.xml index 5ebddca..b7dcd3f 100644 --- a/app/src/main/res/drawable/ic_delete.xml +++ b/app/src/main/res/drawable/ic_delete.xml @@ -5,6 +5,6 @@ android:viewportWidth="24" android:viewportHeight="24"> \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_edit.xml b/app/src/main/res/drawable/ic_edit.xml index ef58b5f..dbf3f9e 100644 --- a/app/src/main/res/drawable/ic_edit.xml +++ b/app/src/main/res/drawable/ic_edit.xml @@ -5,5 +5,5 @@ android:viewportHeight="24"> + android:fillColor="#fff"/> diff --git a/app/src/main/res/drawable/ic_list.xml b/app/src/main/res/drawable/ic_list.xml index 6a72bb2..f622980 100644 --- a/app/src/main/res/drawable/ic_list.xml +++ b/app/src/main/res/drawable/ic_list.xml @@ -5,5 +5,5 @@ android:viewportHeight="16"> + android:fillColor="#ffffff"/> diff --git a/app/src/main/res/drawable/ic_more.xml b/app/src/main/res/drawable/ic_more.xml index 86f2ecd..4c63ce5 100644 --- a/app/src/main/res/drawable/ic_more.xml +++ b/app/src/main/res/drawable/ic_more.xml @@ -2,7 +2,7 @@ android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp"> - + diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml index 8939319..e48feb7 100644 --- a/app/src/main/res/drawable/ic_settings.xml +++ b/app/src/main/res/drawable/ic_settings.xml @@ -5,5 +5,5 @@ android:viewportHeight="24"> + android:fillColor="#fff"/> diff --git a/app/src/main/res/layout/activity_hidden.xml b/app/src/main/res/layout/activity_hidden.xml index a3ab21e..cddf2a1 100644 --- a/app/src/main/res/layout/activity_hidden.xml +++ b/app/src/main/res/layout/activity_hidden.xml @@ -23,6 +23,7 @@ android:layout_width="40dp" android:layout_height="40dp" android:src="@drawable/ic_back" + app:tint="@color/svgTintColor" android:scaleType="fitCenter" android:background="#00000000" android:padding="8dp" @@ -38,17 +39,18 @@ android:layout_weight="1" android:id="@+id/folderName"/> - - - - - diff --git a/app/src/main/res/layout/activity_view_folder.xml b/app/src/main/res/layout/activity_view_folder.xml index a1f208d..2f6a484 100644 --- a/app/src/main/res/layout/activity_view_folder.xml +++ b/app/src/main/res/layout/activity_view_folder.xml @@ -23,6 +23,7 @@ android:layout_width="40dp" android:layout_height="40dp" android:src="@drawable/ic_back" + app:tint="@color/svgTintColor" android:scaleType="fitCenter" android:background="#00000000" android:padding="8dp" @@ -39,11 +40,12 @@ android:id="@+id/folderName"/> - diff --git a/app/src/main/res/layout/item_folder.xml b/app/src/main/res/layout/item_folder.xml index 083fdd8..952ecfc 100644 --- a/app/src/main/res/layout/item_folder.xml +++ b/app/src/main/res/layout/item_folder.xml @@ -3,6 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" + android:layout_marginHorizontal="2dp" android:layout_height="wrap_content">