From 6d53ce0cfee03bc7289c608ec95fcf4a53375bf1 Mon Sep 17 00:00:00 2001 From: "Jan C. Borchardt" <925062+jancborchardt@users.noreply.github.com> Date: Mon, 12 Oct 2026 21:31:51 +0200 Subject: [PATCH 1/6] feat(files): show actions for current folder in toolbar Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: Jan C. Borchardt <925062+jancborchardt@users.noreply.github.com> --- .../nextcloud/ui/fileactions/FileAction.kt | 20 +++++++ .../ui/fileactions/FileActionsBottomSheet.kt | 33 +++++++---- .../ui/activity/FileDisplayActivity.kt | 26 +++++++++ .../android/ui/adapter/OCFileListAdapter.java | 5 ++ .../ui/fragment/OCFileListFragment.java | 21 +++++++ .../main/res/menu/activity_file_display.xml | 7 +++ .../ui/fileactions/FileActionTest.kt | 57 +++++++++++++++++++ 7 files changed, 159 insertions(+), 10 deletions(-) create mode 100644 app/src/test/java/com/nextcloud/ui/fileactions/FileActionTest.kt diff --git a/app/src/main/java/com/nextcloud/ui/fileactions/FileAction.kt b/app/src/main/java/com/nextcloud/ui/fileactions/FileAction.kt index eb69869263bb..e5310f9129f2 100644 --- a/app/src/main/java/com/nextcloud/ui/fileactions/FileAction.kt +++ b/app/src/main/java/com/nextcloud/ui/fileactions/FileAction.kt @@ -73,6 +73,12 @@ enum class FileAction( constructor(id: Int, title: Int) : this(id, title, null) + fun toggledFavorite(): FileAction? = when (this) { + FAVORITE -> UNSET_FAVORITE + UNSET_FAVORITE -> FAVORITE + else -> null + } + companion object { /** * All file actions, in the order they should be displayed @@ -209,6 +215,20 @@ enum class FileAction( return result.toList() } + fun getCurrentFolderActionsToHide(folder: OCFile): List { + val currentFolderActions = setOf( + R.id.action_send_share_file, + R.id.action_favorite, + R.id.action_unset_favorite, + R.id.action_see_details, + R.id.action_sync_file, + R.id.action_encrypted + ) + + return getFileListActionsToHide(setOf(folder)) + + entries.map { it.id }.filterNot { it in currentFolderActions } + } + fun getActionsToHide(files: Set): List { if (files.isEmpty()) return emptyList() diff --git a/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt b/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt index 771ce411b314..62fc590b75fc 100644 --- a/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt +++ b/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt @@ -320,17 +320,18 @@ class FileActionsBottomSheet : val itemBinding = FileActionsBottomSheetItemBinding.inflate(layoutInflater, binding.fileActionsList, false) .apply { root.setOnClickListener { - viewModel.onClick(action) - } - text.setText(action.title) - if (action.icon != null) { - val drawable = - viewThemeUtils.platform.tintDrawable( - requireContext(), - AppCompatResources.getDrawable(requireContext(), action.icon)!! - ) - icon.setImageDrawable(drawable) + val toggledAction = action.toggledFavorite() + if (toggledAction == null) { + viewModel.onClick(action) + return@setOnClickListener + } + + root.isClickable = false + showAction(toggledAction, viewThemeUtils) + val delay = resources.getInteger(android.R.integer.config_mediumAnimTime).toLong() + root.postDelayed({ viewModel.onClick(action) }, delay) } + showAction(action, viewThemeUtils) } return itemBinding.root } @@ -382,3 +383,15 @@ class FileActionsBottomSheet : } } } + +private fun FileActionsBottomSheetItemBinding.showAction(action: FileAction, viewThemeUtils: ViewThemeUtils) { + text.setText(action.title) + if (action.icon != null) { + val context = root.context + val drawable = viewThemeUtils.platform.tintDrawable( + context, + AppCompatResources.getDrawable(context, action.icon)!! + ) + icon.setImageDrawable(drawable) + } +} diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index af7ac237f9d9..f006bd66f6e3 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -369,6 +369,7 @@ class FileDisplayActivity : private fun initUI() { setupHomeSearchToolbarWithSortAndListButtons() mMenuButton.setOnClickListener { v: View? -> openDrawer() } + findViewById(R.id.toolbar).setOnClickListener { openCurrentFolderActions() } mSwitchAccountButton.setOnClickListener { v: View? -> showManageAccountsDialog() } mNotificationButton.setOnClickListener { pushFragment(NavigatorScreen.Notifications) @@ -950,9 +951,24 @@ class FileDisplayActivity : } } + menu?.findItem(R.id.action_current_folder_actions)?.isVisible = canShowCurrentFolderActions() + return super.onPrepareOptionsMenu(menu) } + private fun canShowCurrentFolderActions(): Boolean = leftFragment is OCFileListFragment && + !isDrawerOpen && + !isSearchOpen() && + !isRoot(getCurrentDir()) && + listOfFilesFragment?.isSearchFragment == false && + !isAlbumItemsFragment + + private fun openCurrentFolderActions() { + if (canShowCurrentFolderActions()) { + listOfFilesFragment?.openCurrentFolderActionsMenu() + } + } + override fun onCreateOptionsMenu(menu: Menu): Boolean { val inflater = menuInflater inflater.inflate(R.menu.activity_file_display, menu) @@ -1355,6 +1371,11 @@ class FileDisplayActivity : true } + R.id.action_current_folder_actions -> { + openCurrentFolderActions() + true + } + else -> super.onOptionsItemSelected(item) } @@ -1886,6 +1907,10 @@ class FileDisplayActivity : Log_OC.d(TAG, "download worker finished") val folder = getFolderFromFolderDownloadWorker(intent) ?: return updateFileDownloadIndicator(FileDownloadIndicator.Downloaded, folder) + + if (folder.fileId == getCurrentDir()?.fileId) { + updateListOfFilesFragment() + } } } // endregion @@ -2073,6 +2098,7 @@ class FileDisplayActivity : chosenFile = file // if no file is passed, current file decides } super.updateActionBarTitleAndHomeButton(chosenFile) + supportInvalidateOptionsMenu() } override fun isDrawerIndicatorAvailable(): Boolean = isRoot(getCurrentDir()) diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java index 4133be999632..a0d09f008656 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java +++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java @@ -268,6 +268,11 @@ public void setFavoriteAttributeForItemID(String remotePath, boolean favorite, b } } + if (currentDirectory != null && currentDirectory.getRemotePath().equals(remotePath)) { + currentDirectory.setFavorite(favorite); + mStorageManager.saveFile(currentDirectory); + } + final OCFile updatedFile = changedFile; new Handler(Looper.getMainLooper()).post(() -> { List sortedFiles = differ.sortForCurrentView(new ArrayList<>(differ.getLatestFiles()), diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java index 41f802839c49..3d6060dfb8fc 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -746,6 +746,27 @@ public void openActionsMenu(final int filesCount, final Set checkedFiles }); } + public void openCurrentFolderActionsMenu() { + final OCFile currentFolder = getCurrentFile(); + if (currentFolder == null || currentFolder.isRootDirectory()) { + return; + } + + final Set checkedFiles = new HashSet<>(); + checkedFiles.add(currentFolder); + + throttler.run("overflowClick", () -> { + final var actionsToHide = FileAction.Companion.getCurrentFolderActionsToHide(currentFolder); + final var childFragmentManager = getChildFragmentManager(); + final var actionBottomSheet = FileActionsBottomSheet.newInstance(currentFolder, true, actionsToHide) + .setResultListener(childFragmentManager, this, (id) -> onFileActionChosen(id, checkedFiles)); + + if (FragmentExtensionsKt.isDialogFragmentReady(this)) { + actionBottomSheet.show(childFragmentManager, "actions"); + } + }); + } + @Override public void newDocument() { ChooseRichDocumentsTemplateDialogFragment.newInstance(mFile, diff --git a/app/src/main/res/menu/activity_file_display.xml b/app/src/main/res/menu/activity_file_display.xml index 023f81739ca1..c81caf65f3ca 100644 --- a/app/src/main/res/menu/activity_file_display.xml +++ b/app/src/main/res/menu/activity_file_display.xml @@ -18,6 +18,13 @@ app:actionViewClass="androidx.appcompat.widget.SearchView" app:showAsAction="ifRoom" /> + + () + whenever(folder.isFolder).thenReturn(true) + whenever(folder.mimeType).thenReturn("DIR") + whenever(folder.remotePath).thenReturn("/folder/") + whenever(folder.canReshare()).thenReturn(true) + whenever(folder.canRename()).thenReturn(true) + whenever(folder.canMove()).thenReturn(true) + whenever(folder.canWrite()).thenReturn(true) + + val toHide = Mockito.mockStatic(MimeTypeMap::class.java).use { mimeTypeMap -> + mimeTypeMap.`when` { MimeTypeMap.getSingleton() }.thenReturn(mock()) + FileAction.getCurrentFolderActionsToHide(folder) + } + val shown = FileAction.getActions(setOf(folder)).map { it.id }.filterNot { it in toHide } + + assertEquals( + listOf( + R.id.action_favorite, + R.id.action_unset_favorite, + R.id.action_see_details, + R.id.action_send_share_file, + R.id.action_sync_file, + R.id.action_encrypted + ), + shown + ) + } + + @Test + fun favoriteActionsToggleToEachOther() { + assertEquals(FileAction.UNSET_FAVORITE, FileAction.FAVORITE.toggledFavorite()) + assertEquals(FileAction.FAVORITE, FileAction.UNSET_FAVORITE.toggledFavorite()) + assertNull(FileAction.SEE_DETAILS.toggledFavorite()) + } +} From f34b77a3e3fbfd3cd8912bfe44d021c46cac13ef Mon Sep 17 00:00:00 2001 From: "Jan C. Borchardt" <925062+jancborchardt@users.noreply.github.com> Date: Mon, 12 Oct 2026 22:08:48 +0200 Subject: [PATCH 2/6] fix(files): avoid list shift when opening details Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: Jan C. Borchardt <925062+jancborchardt@users.noreply.github.com> --- .../ui/fileactions/FileActionsBottomSheet.kt | 32 +++++++++++-------- .../ui/activity/FileDisplayActivity.kt | 2 ++ 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt b/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt index 62fc590b75fc..9ea47476ce89 100644 --- a/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt +++ b/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt @@ -23,7 +23,6 @@ import androidx.core.os.bundleOf import androidx.core.view.isNotEmpty import androidx.core.view.isVisible import androidx.fragment.app.FragmentManager -import androidx.fragment.app.setFragmentResult import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ViewModelProvider import com.google.android.material.bottomsheet.BottomSheetBehavior @@ -320,16 +319,8 @@ class FileActionsBottomSheet : val itemBinding = FileActionsBottomSheetItemBinding.inflate(layoutInflater, binding.fileActionsList, false) .apply { root.setOnClickListener { - val toggledAction = action.toggledFavorite() - if (toggledAction == null) { - viewModel.onClick(action) - return@setOnClickListener - } - - root.isClickable = false - showAction(toggledAction, viewThemeUtils) - val delay = resources.getInteger(android.R.integer.config_mediumAnimTime).toLong() - root.postDelayed({ viewModel.onClick(action) }, delay) + action.toggledFavorite()?.let { showAction(it, viewThemeUtils) } + viewModel.onClick(action) } showAction(action, viewThemeUtils) } @@ -337,11 +328,24 @@ class FileActionsBottomSheet : } private fun dispatchActionClick(id: Int?) { - if (id != null) { - setFragmentResult(REQUEST_KEY, bundleOf(RESULT_KEY_ACTION_ID to id)) - parentFragmentManager.clearFragmentResultListener(REQUEST_KEY) + if (id == null) { + return + } + + // get the manager and dismiss before sending, as the result can replace the fragment hosting this sheet + val fragmentManager = parentFragmentManager + val isFavoriteToggle = id == R.id.action_favorite || id == R.id.action_unset_favorite + if (!isFavoriteToggle) { dismiss() } + + fragmentManager.setFragmentResult(REQUEST_KEY, bundleOf(RESULT_KEY_ACTION_ID to id)) + fragmentManager.clearFragmentResultListener(REQUEST_KEY) + + if (isFavoriteToggle) { + val delay = resources.getInteger(android.R.integer.config_mediumAnimTime).toLong() + binding.root.postDelayed({ dismissAllowingStateLoss() }, delay) + } } companion object { diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index f006bd66f6e3..1d41325f6b98 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -2063,6 +2063,8 @@ class FileDisplayActivity : val detailFragment: Fragment = FileDetailFragment.newInstance(file, currentUser, activeTab) setLeftFragment(detailFragment, false) + // swap now, so no frame shows the list without its sort row + supportFragmentManager.executePendingTransactions() configureToolbarForPreview(file) } From 5055c412b96f713391c5dfc2c02a34ad5cc2f566 Mon Sep 17 00:00:00 2001 From: "Jan C. Borchardt" <925062+jancborchardt@users.noreply.github.com> Date: Mon, 12 Oct 2026 22:16:08 +0200 Subject: [PATCH 3/6] feat(files): show syncing icon on files while folder downloads Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: Jan C. Borchardt <925062+jancborchardt@users.noreply.github.com> --- .../FolderDownloadEventBroadcaster.kt | 9 ++++++ .../folderDownload/FolderDownloadWorker.kt | 5 ++-- .../ui/activity/FileDisplayActivity.kt | 30 +++++++++++++++++-- .../android/ui/adapter/OCFileListDelegate.kt | 2 ++ 4 files changed, 41 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/jobs/folderDownload/FolderDownloadEventBroadcaster.kt b/app/src/main/java/com/nextcloud/client/jobs/folderDownload/FolderDownloadEventBroadcaster.kt index 7bd36992902e..b80fb9457d9f 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/folderDownload/FolderDownloadEventBroadcaster.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/folderDownload/FolderDownloadEventBroadcaster.kt @@ -23,6 +23,7 @@ class FolderDownloadEventBroadcaster( const val ACTION_DOWNLOAD_ENQUEUED = PREFIX + "ACTION_DOWNLOAD_ENQUEUED" const val ACTION_DOWNLOAD_COMPLETED = PREFIX + "ACTION_DOWNLOAD_COMPLETED" + const val ACTION_FILE_DOWNLOADED = PREFIX + "ACTION_FILE_DOWNLOADED" const val EXTRA_FILE_ID = PREFIX + "EXTRA_FILE_ID" } @@ -46,4 +47,12 @@ class FolderDownloadEventBroadcaster( broadcastManager.sendBroadcast(intent) } + + fun sendFileDownloaded(id: Long) { + val intent = Intent(ACTION_FILE_DOWNLOADED).apply { + putExtra(EXTRA_FILE_ID, id) + } + + broadcastManager.sendBroadcast(intent) + } } diff --git a/app/src/main/java/com/nextcloud/client/jobs/folderDownload/FolderDownloadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/folderDownload/FolderDownloadWorker.kt index a3856b7e5e25..6020b39309ad 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/folderDownload/FolderDownloadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/folderDownload/FolderDownloadWorker.kt @@ -82,8 +82,8 @@ class FolderDownloadWorker( Log_OC.d(TAG, "🕒 started for ${user.accountName} | folder=${folder.fileName}") trySetForeground(folder) - folderDownloadEventBroadcaster.sendDownloadEnqueued(folder.fileId) pendingDownloads.add(folder.fileId) + folderDownloadEventBroadcaster.sendDownloadEnqueued(folder.fileId) val downloadHelper = FileDownloadHelper.instance() @@ -125,6 +125,7 @@ class FolderDownloadWorker( if (operationResult?.isSuccess == true && operation.downloadType === DownloadType.DOWNLOAD) { getOCFile(operation)?.let { ocFile -> downloadHelper.saveFile(ocFile, operation, storageManager) + folderDownloadEventBroadcaster.sendFileDownloaded(ocFile.fileId) } } @@ -148,8 +149,8 @@ class FolderDownloadWorker( notificationManager.showCompletionNotification(folder.fileName, false) Result.failure() } finally { - folderDownloadEventBroadcaster.sendDownloadCompleted(folder.fileId) pendingDownloads.remove(folder.fileId) + folderDownloadEventBroadcaster.sendDownloadCompleted(folder.fileId) // delay so that user can see the error or success notification delay(2000) diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index 1d41325f6b98..61bc2224213f 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -214,6 +214,7 @@ class FileDisplayActivity : private val folderDownloadStartedReceiver = FolderDownloadStartedReceiver() private val folderDownloadCompletedReceiver = FolderDownloadCompletedReceiver() + private val folderFileDownloadedReceiver = FolderFileDownloadedReceiver() private lateinit var albumOperationListener: AlbumOperationListener @@ -1544,6 +1545,10 @@ class FileDisplayActivity : IntentFilter(FolderDownloadEventBroadcaster.ACTION_DOWNLOAD_COMPLETED) registerReceiver(folderDownloadCompletedReceiver, folderDownloadFinishedIntentFilter) + val folderFileDownloadedIntentFilter = + IntentFilter(FolderDownloadEventBroadcaster.ACTION_FILE_DOWNLOADED) + registerReceiver(folderFileDownloadedReceiver, folderFileDownloadedIntentFilter) + val fileDownloadStartedIntentFilter = IntentFilter(FileDownloadEventBroadcaster.ACTION_DOWNLOAD_ENQUEUED) registerReceiver(fileDownloadStartedReceiver, fileDownloadStartedIntentFilter) @@ -1570,6 +1575,7 @@ class FileDisplayActivity : unregisterReceiver(fileDownloadCompletedReceiver) unregisterReceiver(folderDownloadStartedReceiver) unregisterReceiver(folderDownloadCompletedReceiver) + unregisterReceiver(folderFileDownloadedReceiver) } } // endregion @@ -1899,6 +1905,9 @@ class FileDisplayActivity : Log_OC.d(TAG, "download worker started") val folder = getFolderFromFolderDownloadWorker(intent) ?: return updateFileDownloadIndicator(FileDownloadIndicator.Downloading, folder) + forEachMissingFileInCurrentDir(folder) { + updateFileDownloadIndicator(FileDownloadIndicator.Downloading, it) + } } } @@ -1907,11 +1916,26 @@ class FileDisplayActivity : Log_OC.d(TAG, "download worker finished") val folder = getFolderFromFolderDownloadWorker(intent) ?: return updateFileDownloadIndicator(FileDownloadIndicator.Downloaded, folder) + forEachMissingFileInCurrentDir(folder) { ocFileListFragment?.adapter?.notifyItemChanged(it) } + } + } - if (folder.fileId == getCurrentDir()?.fileId) { - updateListOfFilesFragment() - } + private inner class FolderFileDownloadedReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val file = getFolderFromFolderDownloadWorker(intent) ?: return + val adapter = ocFileListFragment?.adapter ?: return + val row = adapter.files.find { it == file } ?: return + row.storagePath = file.storagePath + adapter.notifyItemChanged(row) + } + } + + private fun forEachMissingFileInCurrentDir(folder: OCFile, action: (OCFile) -> Unit) { + if (folder.fileId != getCurrentDir()?.fileId) { + return } + + ocFileListFragment?.adapter?.files?.filter { !it.isFolder && !it.isDown }?.forEach(action) } // endregion diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt index b1e713211dd0..86c7309f787b 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt @@ -16,6 +16,7 @@ import com.elyeproj.loaderviewlibrary.LoaderImageView import com.nextcloud.android.common.ui.theme.utils.ColorRole import com.nextcloud.client.account.User import com.nextcloud.client.jobs.download.FileDownloadHelper +import com.nextcloud.client.jobs.folderDownload.FolderDownloadWorker import com.nextcloud.client.jobs.gallery.GalleryImageGenerationJob import com.nextcloud.client.jobs.gallery.GalleryImageGenerationListener import com.nextcloud.client.jobs.upload.FileUploadHelper @@ -340,6 +341,7 @@ class OCFileListDelegate( return operationsServiceBinder?.isSynchronizing(user, file) == true || fileDownloadHelper.isDownloading(user, file) || + (!file.isFolder && !file.isDown && FolderDownloadWorker.isDownloading(file.parentId)) || fileUploadHelper.isUploading(file.remotePath, user.accountName) } From ca1cbb3b02444047b236f6c0c02d34dd266db5b2 Mon Sep 17 00:00:00 2001 From: "Jan C. Borchardt" <925062+jancborchardt@users.noreply.github.com> Date: Mon, 12 Oct 2026 22:16:15 +0200 Subject: [PATCH 4/6] fix(files): hide synced icon after deleting local copy Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: Jan C. Borchardt <925062+jancborchardt@users.noreply.github.com> --- .../com/owncloud/android/ui/activity/FileDisplayActivity.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index 61bc2224213f..041350c76a55 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -2343,7 +2343,9 @@ class FileDisplayActivity : resetScrollingAndUpdateActionBar() } - if (leftFragment is OCFileListFragment && !operation.onlyLocalCopy) { + if (leftFragment is OCFileListFragment && operation.onlyLocalCopy) { + leftFragment.adapter?.notifyItemChanged(removedFile) + } else if (leftFragment is OCFileListFragment) { leftFragment.adapter?.removeFile(removedFile) if (leftFragment.adapter?.isEmpty == true) { From 5817deeae180cc4a927f596ac72a42c815cf44bf Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 13 Oct 2026 15:28:46 +0200 Subject: [PATCH 5/6] revert file download indicator since out of scope of the feature and for folder download worker can cause issue Signed-off-by: alperozturk96 --- .../FolderDownloadEventBroadcaster.kt | 9 ------ .../folderDownload/FolderDownloadWorker.kt | 5 ++-- .../ui/activity/FileDisplayActivity.kt | 30 ++----------------- .../android/ui/adapter/OCFileListDelegate.kt | 2 -- 4 files changed, 5 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/jobs/folderDownload/FolderDownloadEventBroadcaster.kt b/app/src/main/java/com/nextcloud/client/jobs/folderDownload/FolderDownloadEventBroadcaster.kt index b80fb9457d9f..7bd36992902e 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/folderDownload/FolderDownloadEventBroadcaster.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/folderDownload/FolderDownloadEventBroadcaster.kt @@ -23,7 +23,6 @@ class FolderDownloadEventBroadcaster( const val ACTION_DOWNLOAD_ENQUEUED = PREFIX + "ACTION_DOWNLOAD_ENQUEUED" const val ACTION_DOWNLOAD_COMPLETED = PREFIX + "ACTION_DOWNLOAD_COMPLETED" - const val ACTION_FILE_DOWNLOADED = PREFIX + "ACTION_FILE_DOWNLOADED" const val EXTRA_FILE_ID = PREFIX + "EXTRA_FILE_ID" } @@ -47,12 +46,4 @@ class FolderDownloadEventBroadcaster( broadcastManager.sendBroadcast(intent) } - - fun sendFileDownloaded(id: Long) { - val intent = Intent(ACTION_FILE_DOWNLOADED).apply { - putExtra(EXTRA_FILE_ID, id) - } - - broadcastManager.sendBroadcast(intent) - } } diff --git a/app/src/main/java/com/nextcloud/client/jobs/folderDownload/FolderDownloadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/folderDownload/FolderDownloadWorker.kt index 6020b39309ad..a3856b7e5e25 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/folderDownload/FolderDownloadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/folderDownload/FolderDownloadWorker.kt @@ -82,8 +82,8 @@ class FolderDownloadWorker( Log_OC.d(TAG, "🕒 started for ${user.accountName} | folder=${folder.fileName}") trySetForeground(folder) - pendingDownloads.add(folder.fileId) folderDownloadEventBroadcaster.sendDownloadEnqueued(folder.fileId) + pendingDownloads.add(folder.fileId) val downloadHelper = FileDownloadHelper.instance() @@ -125,7 +125,6 @@ class FolderDownloadWorker( if (operationResult?.isSuccess == true && operation.downloadType === DownloadType.DOWNLOAD) { getOCFile(operation)?.let { ocFile -> downloadHelper.saveFile(ocFile, operation, storageManager) - folderDownloadEventBroadcaster.sendFileDownloaded(ocFile.fileId) } } @@ -149,8 +148,8 @@ class FolderDownloadWorker( notificationManager.showCompletionNotification(folder.fileName, false) Result.failure() } finally { - pendingDownloads.remove(folder.fileId) folderDownloadEventBroadcaster.sendDownloadCompleted(folder.fileId) + pendingDownloads.remove(folder.fileId) // delay so that user can see the error or success notification delay(2000) diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index 041350c76a55..8fc39287dd64 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -214,7 +214,6 @@ class FileDisplayActivity : private val folderDownloadStartedReceiver = FolderDownloadStartedReceiver() private val folderDownloadCompletedReceiver = FolderDownloadCompletedReceiver() - private val folderFileDownloadedReceiver = FolderFileDownloadedReceiver() private lateinit var albumOperationListener: AlbumOperationListener @@ -1545,10 +1544,6 @@ class FileDisplayActivity : IntentFilter(FolderDownloadEventBroadcaster.ACTION_DOWNLOAD_COMPLETED) registerReceiver(folderDownloadCompletedReceiver, folderDownloadFinishedIntentFilter) - val folderFileDownloadedIntentFilter = - IntentFilter(FolderDownloadEventBroadcaster.ACTION_FILE_DOWNLOADED) - registerReceiver(folderFileDownloadedReceiver, folderFileDownloadedIntentFilter) - val fileDownloadStartedIntentFilter = IntentFilter(FileDownloadEventBroadcaster.ACTION_DOWNLOAD_ENQUEUED) registerReceiver(fileDownloadStartedReceiver, fileDownloadStartedIntentFilter) @@ -1575,7 +1570,6 @@ class FileDisplayActivity : unregisterReceiver(fileDownloadCompletedReceiver) unregisterReceiver(folderDownloadStartedReceiver) unregisterReceiver(folderDownloadCompletedReceiver) - unregisterReceiver(folderFileDownloadedReceiver) } } // endregion @@ -1905,9 +1899,6 @@ class FileDisplayActivity : Log_OC.d(TAG, "download worker started") val folder = getFolderFromFolderDownloadWorker(intent) ?: return updateFileDownloadIndicator(FileDownloadIndicator.Downloading, folder) - forEachMissingFileInCurrentDir(folder) { - updateFileDownloadIndicator(FileDownloadIndicator.Downloading, it) - } } } @@ -1916,26 +1907,11 @@ class FileDisplayActivity : Log_OC.d(TAG, "download worker finished") val folder = getFolderFromFolderDownloadWorker(intent) ?: return updateFileDownloadIndicator(FileDownloadIndicator.Downloaded, folder) - forEachMissingFileInCurrentDir(folder) { ocFileListFragment?.adapter?.notifyItemChanged(it) } - } - } - private inner class FolderFileDownloadedReceiver : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - val file = getFolderFromFolderDownloadWorker(intent) ?: return - val adapter = ocFileListFragment?.adapter ?: return - val row = adapter.files.find { it == file } ?: return - row.storagePath = file.storagePath - adapter.notifyItemChanged(row) - } - } - - private fun forEachMissingFileInCurrentDir(folder: OCFile, action: (OCFile) -> Unit) { - if (folder.fileId != getCurrentDir()?.fileId) { - return + if (folder.fileId == getCurrentDir()?.fileId) { + updateListOfFilesFragment() + } } - - ocFileListFragment?.adapter?.files?.filter { !it.isFolder && !it.isDown }?.forEach(action) } // endregion diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt index 86c7309f787b..b1e713211dd0 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt @@ -16,7 +16,6 @@ import com.elyeproj.loaderviewlibrary.LoaderImageView import com.nextcloud.android.common.ui.theme.utils.ColorRole import com.nextcloud.client.account.User import com.nextcloud.client.jobs.download.FileDownloadHelper -import com.nextcloud.client.jobs.folderDownload.FolderDownloadWorker import com.nextcloud.client.jobs.gallery.GalleryImageGenerationJob import com.nextcloud.client.jobs.gallery.GalleryImageGenerationListener import com.nextcloud.client.jobs.upload.FileUploadHelper @@ -341,7 +340,6 @@ class OCFileListDelegate( return operationsServiceBinder?.isSynchronizing(user, file) == true || fileDownloadHelper.isDownloading(user, file) || - (!file.isFolder && !file.isDown && FolderDownloadWorker.isDownloading(file.parentId)) || fileUploadHelper.isUploading(file.remotePath, user.accountName) } From 9fba721686bbe3a424487d8294317ebfe5d56518 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 13 Oct 2026 15:29:36 +0200 Subject: [PATCH 6/6] remove tests since its visible ui action Signed-off-by: alperozturk96 --- .../ui/fileactions/FileActionTest.kt | 57 ------------------- 1 file changed, 57 deletions(-) delete mode 100644 app/src/test/java/com/nextcloud/ui/fileactions/FileActionTest.kt diff --git a/app/src/test/java/com/nextcloud/ui/fileactions/FileActionTest.kt b/app/src/test/java/com/nextcloud/ui/fileactions/FileActionTest.kt deleted file mode 100644 index be5bdff6880f..000000000000 --- a/app/src/test/java/com/nextcloud/ui/fileactions/FileActionTest.kt +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ -package com.nextcloud.ui.fileactions - -import android.webkit.MimeTypeMap -import com.owncloud.android.R -import com.owncloud.android.datamodel.OCFile -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNull -import org.junit.Test -import org.mockito.Mockito -import org.mockito.kotlin.mock -import org.mockito.kotlin.whenever - -class FileActionTest { - - @Test - fun currentFolderShowsOnlySafeActionsInMenuOrder() { - val folder = mock() - whenever(folder.isFolder).thenReturn(true) - whenever(folder.mimeType).thenReturn("DIR") - whenever(folder.remotePath).thenReturn("/folder/") - whenever(folder.canReshare()).thenReturn(true) - whenever(folder.canRename()).thenReturn(true) - whenever(folder.canMove()).thenReturn(true) - whenever(folder.canWrite()).thenReturn(true) - - val toHide = Mockito.mockStatic(MimeTypeMap::class.java).use { mimeTypeMap -> - mimeTypeMap.`when` { MimeTypeMap.getSingleton() }.thenReturn(mock()) - FileAction.getCurrentFolderActionsToHide(folder) - } - val shown = FileAction.getActions(setOf(folder)).map { it.id }.filterNot { it in toHide } - - assertEquals( - listOf( - R.id.action_favorite, - R.id.action_unset_favorite, - R.id.action_see_details, - R.id.action_send_share_file, - R.id.action_sync_file, - R.id.action_encrypted - ), - shown - ) - } - - @Test - fun favoriteActionsToggleToEachOther() { - assertEquals(FileAction.UNSET_FAVORITE, FileAction.FAVORITE.toggledFavorite()) - assertEquals(FileAction.FAVORITE, FileAction.UNSET_FAVORITE.toggledFavorite()) - assertNull(FileAction.SEE_DETAILS.toggledFavorite()) - } -}