From 69cc9ea479ace957c37095e175556e5339bb35d3 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 27 Mar 2026 14:56:21 +0100 Subject: [PATCH 01/11] Rename .java to .kt Signed-off-by: alperozturk96 --- .../ui/adapter/{UploadListAdapter.java => UploadListAdapter.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/src/main/java/com/owncloud/android/ui/adapter/{UploadListAdapter.java => UploadListAdapter.kt} (100%) diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.kt similarity index 100% rename from app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.java rename to app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.kt From a31e0022ff26686b2b9a0908ec59474f4050b959 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 27 Mar 2026 14:56:22 +0100 Subject: [PATCH 02/11] refactor(upload-list-adapter) convert to kotlin simplify logic Signed-off-by: alperozturk96 --- .../client/jobs/upload/FileUploadHelper.kt | 6 +- .../utils/extensions/OCUploadExtensions.kt | 8 + .../owncloud/android/db/OCUploadComparator.kt | 59 - .../android/ui/adapter/UploadListAdapter.kt | 1346 +++++++++-------- .../android/ui/db/OCUploadComparatorTest.kt | 232 +-- 5 files changed, 783 insertions(+), 868 deletions(-) delete mode 100644 app/src/main/java/com/owncloud/android/db/OCUploadComparator.kt diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt index 634c60827d1c..f8ebfe9a97de 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt @@ -172,7 +172,7 @@ class FileUploadHelper { connectivityService: ConnectivityService, accountManager: UserAccountManager, powerManagementService: PowerManagementService, - uploads: Array + uploads: List ): Boolean { var showNotExistMessage = false var showSyncConflictNotification = false @@ -356,7 +356,7 @@ class FileUploadHelper { status: UploadStatus, capability: OCCapability, nameCollisionPolicy: NameCollisionPolicy? = null, - onCompleted: (Array) -> Unit + onCompleted: (List) -> Unit ) { ioScope.launch { val dao = uploadsStorageManager.uploadDao @@ -366,7 +366,7 @@ class FileUploadHelper { dao.getUploadsByStatus(status.value, nameCollisionPolicy?.serialize()) }.mapNotNull { it.toOCUpload(capability) - }.toTypedArray() + } onCompleted(result) } } diff --git a/app/src/main/java/com/nextcloud/utils/extensions/OCUploadExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/OCUploadExtensions.kt index 6b2550df1e60..583184c78f9a 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/OCUploadExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/OCUploadExtensions.kt @@ -12,3 +12,11 @@ import com.owncloud.android.db.OCUpload fun List.getUploadIds(): LongArray = map { it.uploadId }.toLongArray() fun Array.getUploadIds(): LongArray = map { it.uploadId }.toLongArray() + +fun List.sortedByUploadOrder(): List = + sortedWith( + compareBy { it.fixedUploadStatus } + .thenByDescending { it.isFixedUploadingNow } + .thenByDescending { it.fixedUploadEndTimeStamp } + .thenBy { it.fixedUploadId } + ) diff --git a/app/src/main/java/com/owncloud/android/db/OCUploadComparator.kt b/app/src/main/java/com/owncloud/android/db/OCUploadComparator.kt deleted file mode 100644 index 5c73c9f93c58..000000000000 --- a/app/src/main/java/com/owncloud/android/db/OCUploadComparator.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2019 Daniele Fognini - * SPDX-License-Identifier: GPL-3.0-or-later - */ -package com.owncloud.android.db - -/** - * Sorts OCUpload by (uploadStatus, uploadingNow, uploadEndTimeStamp, uploadId). - */ -class OCUploadComparator : Comparator { - @Suppress("ReturnCount") - override fun compare(upload1: OCUpload?, upload2: OCUpload?): Int { - if (upload1 == null && upload2 == null) { - return 0 - } - if (upload1 == null) { - return -1 - } - if (upload2 == null) { - return 1 - } - - val compareUploadStatus = compareUploadStatus(upload1, upload2) - if (compareUploadStatus != 0) { - return compareUploadStatus - } - - val compareUploadingNow = compareUploadingNow(upload1, upload2) - if (compareUploadingNow != 0) { - return compareUploadingNow - } - - val compareUpdateTime = compareUpdateTime(upload1, upload2) - if (compareUpdateTime != 0) { - return compareUpdateTime - } - - val compareUploadId = compareUploadId(upload1, upload2) - if (compareUploadId != 0) { - return compareUploadId - } - - return 0 - } - - private fun compareUploadStatus(upload1: OCUpload, upload2: OCUpload): Int = - upload1.fixedUploadStatus.compareTo(upload2.fixedUploadStatus) - - private fun compareUploadingNow(upload1: OCUpload, upload2: OCUpload): Int = - upload2.isFixedUploadingNow.compareTo(upload1.isFixedUploadingNow) - - private fun compareUpdateTime(upload1: OCUpload, upload2: OCUpload): Int = - upload2.fixedUploadEndTimeStamp.compareTo(upload1.fixedUploadEndTimeStamp) - - private fun compareUploadId(upload1: OCUpload, upload2: OCUpload): Int = - upload1.fixedUploadId.compareTo(upload2.fixedUploadId) -} diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.kt index d0f239382757..6380fc17cc7f 100755 --- a/app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.kt @@ -1,962 +1,992 @@ /* * Nextcloud - Android Client * - * SPDX-FileCopyrightText: 2019 Chris Narkiewicz - * SPDX-FileCopyrightText: 2018 Tobias Kaminsky - * SPDX-FileCopyrightText: 2018 Nextcloud - * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later */ -package com.owncloud.android.ui.adapter; - -import android.annotation.SuppressLint; -import android.app.NotificationManager; -import android.content.ActivityNotFoundException; -import android.content.Context; -import android.content.Intent; -import android.graphics.Bitmap; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.text.format.DateUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.PopupMenu; - -import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter; -import com.afollestad.sectionedrecyclerview.SectionedViewHolder; -import com.nextcloud.client.account.User; -import com.nextcloud.client.account.UserAccountManager; -import com.nextcloud.client.core.Clock; -import com.nextcloud.client.device.PowerManagementService; -import com.nextcloud.client.jobs.upload.FileUploadHelper; -import com.nextcloud.client.jobs.upload.FileUploadWorker; -import com.nextcloud.client.network.ConnectivityService; -import com.nextcloud.utils.extensions.ViewExtensionsKt; -import com.owncloud.android.MainApp; -import com.owncloud.android.R; -import com.owncloud.android.databinding.UploadListHeaderBinding; -import com.owncloud.android.databinding.UploadListItemBinding; -import com.owncloud.android.datamodel.FileDataStorageManager; -import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.datamodel.ThumbnailsCacheManager; -import com.owncloud.android.datamodel.UploadsStorageManager; -import com.owncloud.android.datamodel.UploadsStorageManager.UploadStatus; -import com.owncloud.android.db.OCUpload; -import com.owncloud.android.db.OCUploadComparator; -import com.owncloud.android.db.UploadResult; -import com.owncloud.android.files.services.NameCollisionPolicy; -import com.owncloud.android.lib.common.operations.OnRemoteOperationListener; -import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.operations.RefreshFolderOperation; -import com.owncloud.android.ui.activity.ConflictsResolveActivity; -import com.owncloud.android.ui.activity.FileActivity; -import com.owncloud.android.ui.activity.FileDisplayActivity; -import com.owncloud.android.ui.adapter.progressListener.UploadProgressListener; -import com.owncloud.android.ui.preview.PreviewImageFragment; -import com.owncloud.android.utils.DisplayUtils; -import com.owncloud.android.utils.MimeTypeUtil; -import com.owncloud.android.utils.theme.ViewThemeUtils; - -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; - -import androidx.annotation.NonNull; -import androidx.core.content.ContextCompat; -import kotlin.Unit; - -/** - * This Adapter populates a ListView with following types of uploads: pending, active, completed. Filtering possible. - */ -public class UploadListAdapter extends SectionedRecyclerViewAdapter { - private static final String TAG = UploadListAdapter.class.getSimpleName(); - - private record Section( - Type type, - int titleRes, - UploadStatus status, - NameCollisionPolicy collisionPolicy, - OCUpload[] items +package com.owncloud.android.ui.adapter + +import android.annotation.SuppressLint +import android.app.NotificationManager +import android.content.ActivityNotFoundException +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.text.format.DateUtils +import android.view.LayoutInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import android.widget.PopupMenu +import androidx.core.content.ContextCompat +import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter +import com.afollestad.sectionedrecyclerview.SectionedViewHolder +import com.nextcloud.client.account.User +import com.nextcloud.client.account.UserAccountManager +import com.nextcloud.client.core.Clock +import com.nextcloud.client.device.PowerManagementService +import com.nextcloud.client.jobs.upload.FileUploadHelper.Companion.buildRemoteName +import com.nextcloud.client.jobs.upload.FileUploadHelper.Companion.instance +import com.nextcloud.client.jobs.upload.FileUploadWorker.Companion.cancelUpload +import com.nextcloud.client.network.ConnectivityService +import com.nextcloud.utils.extensions.setVisibleIf +import com.nextcloud.utils.extensions.sortedByUploadOrder +import com.owncloud.android.MainApp +import com.owncloud.android.R +import com.owncloud.android.databinding.UploadListHeaderBinding +import com.owncloud.android.databinding.UploadListItemBinding +import com.owncloud.android.datamodel.FileDataStorageManager +import com.owncloud.android.datamodel.OCFile +import com.owncloud.android.datamodel.ThumbnailsCacheManager +import com.owncloud.android.datamodel.ThumbnailsCacheManager.AsyncThumbnailDrawable +import com.owncloud.android.datamodel.ThumbnailsCacheManager.ThumbnailGenerationTask +import com.owncloud.android.datamodel.ThumbnailsCacheManager.ThumbnailGenerationTaskObject +import com.owncloud.android.datamodel.UploadsStorageManager +import com.owncloud.android.datamodel.UploadsStorageManager.UploadStatus +import com.owncloud.android.db.OCUpload +import com.owncloud.android.db.UploadResult +import com.owncloud.android.files.services.NameCollisionPolicy +import com.owncloud.android.lib.common.operations.OnRemoteOperationListener +import com.owncloud.android.lib.common.operations.RemoteOperation +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.operations.RefreshFolderOperation +import com.owncloud.android.ui.activity.ConflictsResolveActivity.Companion.createIntent +import com.owncloud.android.ui.activity.FileActivity +import com.owncloud.android.ui.activity.FileDisplayActivity +import com.owncloud.android.ui.activity.FileDisplayActivity.Companion.openFileIntent +import com.owncloud.android.ui.adapter.progressListener.UploadProgressListener +import com.owncloud.android.ui.preview.PreviewImageFragment.Companion.canBePreviewed +import com.owncloud.android.utils.DisplayUtils +import com.owncloud.android.utils.MimeTypeUtil +import com.owncloud.android.utils.theme.ViewThemeUtils +import java.io.File +import java.util.function.Consumer +import java.util.function.Supplier + +class UploadListAdapter( + fileActivity: FileActivity, + uploadsStorageManager: UploadsStorageManager, + storageManager: FileDataStorageManager, + accountManager: UserAccountManager, + connectivityService: ConnectivityService, + powerManagementService: PowerManagementService, + clock: Clock, + viewThemeUtils: ViewThemeUtils +) : SectionedRecyclerViewAdapter() { + + private data class Section( + val type: Type?, + val titleRes: Int, + val status: UploadStatus?, + val collisionPolicy: NameCollisionPolicy?, + val items: List ) { - Section withItems(OCUpload[] newItems) { - return new Section(type, titleRes, status, collisionPolicy, newItems); - } + fun withItems(newItems: List) = copy(items = newItems) } - private final List
sections = new ArrayList<>(List.of( - new Section(Type.CURRENT, R.string.uploads_view_group_current_uploads, UploadStatus.UPLOAD_IN_PROGRESS, null, new OCUpload[0]), - new Section(Type.FAILED, R.string.uploads_view_group_failed_uploads, UploadStatus.UPLOAD_FAILED, null, new OCUpload[0]), - new Section(Type.CANCELLED, R.string.uploads_view_group_manually_cancelled_uploads, UploadStatus.UPLOAD_CANCELLED, null, new OCUpload[0]), - new Section(Type.COMPLETED, R.string.uploads_view_group_completed_uploads, UploadStatus.UPLOAD_SUCCEEDED, NameCollisionPolicy.ASK_USER, new OCUpload[0]), - new Section(Type.SKIPPED, R.string.uploads_view_upload_status_skip, UploadStatus.UPLOAD_SUCCEEDED, NameCollisionPolicy.SKIP, new OCUpload[0]))); - - private UploadProgressListener uploadProgressListener; - private final FileActivity parentActivity; - private final UploadsStorageManager uploadsStorageManager; - private final FileDataStorageManager storageManager; - private final ConnectivityService connectivityService; - private final PowerManagementService powerManagementService; - private final UserAccountManager accountManager; - private final Clock clock; - private final boolean showUser; - private final ViewThemeUtils viewThemeUtils; - private NotificationManager mNotificationManager; - private final FileUploadHelper uploadHelper = FileUploadHelper.Companion.instance(); - - public UploadListAdapter(final FileActivity fileActivity, - final UploadsStorageManager uploadsStorageManager, - final FileDataStorageManager storageManager, - final UserAccountManager accountManager, - final ConnectivityService connectivityService, - final PowerManagementService powerManagementService, - final Clock clock, - final ViewThemeUtils viewThemeUtils) { - Log_OC.d(TAG, "UploadListAdapter"); - - this.parentActivity = fileActivity; - this.uploadsStorageManager = uploadsStorageManager; - this.storageManager = storageManager; - this.accountManager = accountManager; - this.connectivityService = connectivityService; - this.powerManagementService = powerManagementService; - this.clock = clock; - this.viewThemeUtils = viewThemeUtils; - shouldShowHeadersForEmptySections(false); - showUser = accountManager.getAccounts().length > 1; + private val sections: MutableList
= ArrayList
( + listOf( + Section( + Type.CURRENT, + R.string.uploads_view_group_current_uploads, + UploadStatus.UPLOAD_IN_PROGRESS, + null, + listOf(), + ), + Section( + Type.FAILED, + R.string.uploads_view_group_failed_uploads, + UploadStatus.UPLOAD_FAILED, + null, + listOf() + ), + Section( + Type.CANCELLED, + R.string.uploads_view_group_manually_cancelled_uploads, + UploadStatus.UPLOAD_CANCELLED, + null, + listOf() + ), + Section( + Type.COMPLETED, + R.string.uploads_view_group_completed_uploads, + UploadStatus.UPLOAD_SUCCEEDED, + NameCollisionPolicy.ASK_USER, + listOf() + ), + Section( + Type.SKIPPED, + R.string.uploads_view_upload_status_skip, + UploadStatus.UPLOAD_SUCCEEDED, + NameCollisionPolicy.SKIP, + listOf() + ) + ) + ) + + private var uploadProgressListener: UploadProgressListener? = null + private val parentActivity: FileActivity + private val uploadsStorageManager: UploadsStorageManager + private val storageManager: FileDataStorageManager + private val connectivityService: ConnectivityService + private val powerManagementService: PowerManagementService + private val accountManager: UserAccountManager + private val clock: Clock + private val showUser: Boolean + private val viewThemeUtils: ViewThemeUtils + private var mNotificationManager: NotificationManager? = null + private val uploadHelper = instance() + + init { + Log_OC.d(TAG, "UploadListAdapter") + + this.parentActivity = fileActivity + this.uploadsStorageManager = uploadsStorageManager + this.storageManager = storageManager + this.accountManager = accountManager + this.connectivityService = connectivityService + this.powerManagementService = powerManagementService + this.clock = clock + this.viewThemeUtils = viewThemeUtils + shouldShowHeadersForEmptySections(false) + showUser = accountManager.getAccounts().size > 1 } - @Override - public int getSectionCount() { - return sections.size(); + override fun getSectionCount(): Int { + return sections.size } - @Override - public int getItemCount(int section) { - return sections.get(section).items().length; + override fun getItemCount(section: Int): Int { + return sections[section].items.size } - @Override - public void onBindHeaderViewHolder(SectionedViewHolder holder, int section, boolean expanded) { - HeaderViewHolder headerViewHolder = (HeaderViewHolder) holder; - - Section group = sections.get(section); - String title = parentActivity.getString(group.titleRes()); - int count = group.items().length; - - headerViewHolder.binding.uploadListTitle.setText( - String.format(parentActivity.getString(R.string.uploads_view_group_header), title, count)); - viewThemeUtils.platform.colorPrimaryTextViewElement(headerViewHolder.binding.uploadListTitle); - - headerViewHolder.binding.uploadListTitle.setOnClickListener(v -> { - toggleSectionExpanded(section); - headerViewHolder.binding.uploadListState.setImageResource(isSectionExpanded(section) ? - R.drawable.ic_expand_less : - R.drawable.ic_expand_more); - }); + override fun onBindHeaderViewHolder(holder: SectionedViewHolder?, section: Int, expanded: Boolean) { + val headerViewHolder = holder as HeaderViewHolder - headerViewHolder.binding.uploadListStateLayout.setOnClickListener(v -> {{ - toggleSectionExpanded(section); - headerViewHolder.binding.uploadListState.setImageResource(isSectionExpanded(section) ? - R.drawable.ic_expand_less : - R.drawable.ic_expand_more); - }}); + val group = sections[section] + val title = parentActivity.getString(group.titleRes) + val count = group.items.size - switch (group.type) { - case CURRENT, COMPLETED -> headerViewHolder.binding.uploadListAction.setImageResource(R.drawable.ic_close); - case CANCELLED, FAILED -> - headerViewHolder.binding.uploadListAction.setImageResource(R.drawable.ic_dots_vertical); + headerViewHolder.binding.uploadListTitle.text = + String.format(parentActivity.getString(R.string.uploads_view_group_header), title, count) + viewThemeUtils.platform.colorTextView(headerViewHolder.binding.uploadListTitle) + headerViewHolder.binding.uploadListTitle.setOnClickListener { + toggleSectionExpanded(section) + val icon = if (isSectionExpanded(section)) R.drawable.ic_expand_less else R.drawable.ic_expand_more + headerViewHolder.binding.uploadListState.setImageResource(icon) } - ViewExtensionsKt.setVisibleIf(headerViewHolder.binding.autoUploadBatterySaverWarningCard.root, - powerManagementService.isPowerSavingEnabled()); - viewThemeUtils.material.themeCardView(headerViewHolder.binding.autoUploadBatterySaverWarningCard.root); - - headerViewHolder.binding.uploadListAction.setOnClickListener(v -> { - switch (group.type) { - case CURRENT -> { - OCUpload[] items = group.items(); - if (items.length == 0) return; - - String accountName = items[0].getAccountName(); + headerViewHolder.binding.uploadListStateLayout.setOnClickListener { + toggleSectionExpanded(section) + val icon = if (isSectionExpanded(section)) R.drawable.ic_expand_less else R.drawable.ic_expand_more + headerViewHolder.binding.uploadListState.setImageResource(icon) + } - final int totalUploads = group.items().length; - final int[] completedCount = {0}; + when (group.type) { + Type.CURRENT, Type.COMPLETED -> headerViewHolder.binding.uploadListAction.setImageResource(R.drawable.ic_close) + Type.CANCELLED, Type.FAILED -> headerViewHolder.binding.uploadListAction.setImageResource(R.drawable.ic_dots_vertical) + else -> {} + } - for (int i=0; i { - FileUploadWorker.Companion.cancelUpload(upload.getRemotePath(), accountName, () -> { - completedCount[0]++; + headerViewHolder.binding.autoUploadBatterySaverWarningCard.root + .setVisibleIf(powerManagementService.isPowerSavingEnabled) + viewThemeUtils.material.themeCardView(headerViewHolder.binding.autoUploadBatterySaverWarningCard.root) + + headerViewHolder.binding.uploadListAction.setOnClickListener { + when (group.type) { + Type.CURRENT -> { + val items = group.items + if (items.isEmpty()) return@setOnClickListener + + val accountName = items[0].accountName + + val totalUploads = group.items.size + val completedCount = intArrayOf(0) + + for (i in group.items.indices) { + val upload = group.items[i] + uploadHelper.updateUploadStatus( + upload.remotePath, + accountName, + UploadStatus.UPLOAD_CANCELLED + ) { + cancelUpload(upload.remotePath, accountName) { + completedCount[0]++ if (completedCount[0] == totalUploads) { - Log_OC.d(TAG, "refreshing upload items"); + Log_OC.d(TAG, "refreshing upload items") // All uploads finished, refresh UI once - loadUploadItemsFromDb(() -> {}); + loadUploadItemsFromDb(Runnable {}) } - return Unit.INSTANCE; - }); - return Unit.INSTANCE; - }); + } + } } } - case COMPLETED -> { - uploadsStorageManager.clearSuccessfulUploads(); - loadUploadItemsFromDb(() -> {}); - } - case FAILED -> showFailedPopupMenu(headerViewHolder); - case CANCELLED -> showCancelledPopupMenu(headerViewHolder); - default -> { + Type.COMPLETED -> { + uploadsStorageManager.clearSuccessfulUploads() + loadUploadItemsFromDb {} } + + Type.FAILED -> showFailedPopupMenu(headerViewHolder) + Type.CANCELLED -> showCancelledPopupMenu(headerViewHolder) + else -> {} } - }); + } } - private void showFailedPopupMenu(HeaderViewHolder headerViewHolder) { - PopupMenu failedPopup = new PopupMenu(MainApp.getAppContext(), headerViewHolder.binding.uploadListAction); - failedPopup.inflate(R.menu.upload_list_failed_options); - failedPopup.setOnMenuItemClickListener(i -> { - int itemId = i.getItemId(); - + private fun showFailedPopupMenu(headerViewHolder: HeaderViewHolder) { + val failedPopup = PopupMenu(MainApp.getAppContext(), headerViewHolder.binding.uploadListAction) + failedPopup.inflate(R.menu.upload_list_failed_options) + failedPopup.setOnMenuItemClickListener { i: MenuItem? -> + val itemId = i!!.itemId if (itemId == R.id.action_upload_list_failed_clear) { - uploadsStorageManager.clearFailedButNotDelayedUploads(); - clearTempEncryptedFolder(); - loadUploadItemsFromDb(() -> {}); + uploadsStorageManager.clearFailedButNotDelayedUploads() + clearTempEncryptedFolder() + loadUploadItemsFromDb {} } else if (itemId == R.id.action_upload_list_failed_retry) { uploadHelper.retryFailedUploads( uploadsStorageManager, connectivityService, accountManager, - powerManagementService); + powerManagementService + ) } + true + } - return true; - }); - - failedPopup.show(); + failedPopup.show() } - private void showCancelledPopupMenu(HeaderViewHolder headerViewHolder) { - PopupMenu popup = new PopupMenu(MainApp.getAppContext(), headerViewHolder.binding.uploadListAction); - popup.inflate(R.menu.upload_list_cancelled_options); - - popup.setOnMenuItemClickListener(i -> { - int itemId = i.getItemId(); + private fun showCancelledPopupMenu(headerViewHolder: HeaderViewHolder) { + val popup = PopupMenu(MainApp.getAppContext(), headerViewHolder.binding.uploadListAction) + popup.inflate(R.menu.upload_list_cancelled_options) + popup.setOnMenuItemClickListener { i: MenuItem -> + val itemId = i.itemId if (itemId == R.id.action_upload_list_cancelled_clear) { - uploadsStorageManager.clearCancelledUploadsForCurrentAccount(); - loadUploadItemsFromDb(() -> {}); - clearTempEncryptedFolder(); + uploadsStorageManager.clearCancelledUploadsForCurrentAccount() + loadUploadItemsFromDb(Runnable {}) + clearTempEncryptedFolder() } else if (itemId == R.id.action_upload_list_cancelled_resume) { - retryCancelledUploads(); + retryCancelledUploads() } + true + } - return true; - }); - - popup.show(); + popup.show() } - private void clearTempEncryptedFolder() { - Optional user = parentActivity.getUser(); - user.ifPresent(value -> FileDataStorageManager.clearTempEncryptedFolder(value.getAccountName())); + private fun clearTempEncryptedFolder() { + val user = parentActivity.user + user.ifPresent(Consumer { value: User? -> FileDataStorageManager.clearTempEncryptedFolder(value!!.accountName) }) } // FIXME For e2e resume is not working - private void retryCancelledUploads() { - new Thread(() -> { - boolean showNotExistMessage = uploadHelper.retryCancelledUploads( + private fun retryCancelledUploads() { + Thread { + val showNotExistMessage = uploadHelper.retryCancelledUploads( uploadsStorageManager, connectivityService, accountManager, - powerManagementService); - parentActivity.runOnUiThread(() -> { + powerManagementService + ) + parentActivity.runOnUiThread { if (showNotExistMessage) { - showNotExistMessage(); + showNotExistMessage() } - }); - }).start(); + } + }.start() } - private void showNotExistMessage() { - DisplayUtils.showSnackMessage(parentActivity, R.string.upload_action_file_not_exist_message); + private fun showNotExistMessage() { + DisplayUtils.showSnackMessage(parentActivity, R.string.upload_action_file_not_exist_message) } - @Override - public void onBindFooterViewHolder(SectionedViewHolder holder, int section) { - // not needed - } + override fun onBindFooterViewHolder(holder: SectionedViewHolder?, section: Int) = Unit - @Override - public void onBindViewHolder(SectionedViewHolder holder, int section, int relativePosition, int absolutePosition) { - if (sections.isEmpty() || section < 0 || section >= sections.size()) { - return; + override fun onBindViewHolder( + holder: SectionedViewHolder?, + section: Int, + relativePosition: Int, + absolutePosition: Int + ) { + if (sections.isEmpty() || section < 0 || section >= sections.size) { + return } - Section sectionData = sections.get(section); - OCUpload item = sectionData.items()[relativePosition]; - - if (item == null) { - return; - } + val sectionData = sections[section] + val item = sectionData.items[relativePosition] - ItemViewHolder itemViewHolder = (ItemViewHolder) holder; - itemViewHolder.binding.uploadName.setText(item.getLocalPath()); + val itemViewHolder = holder as ItemViewHolder + itemViewHolder.binding.uploadName.text = item.localPath // local file name - File remoteFile = new File(item.getRemotePath()); - String fileName = remoteFile.getName(); + val remoteFile = File(item.remotePath) + var fileName = remoteFile.getName() if (fileName.isEmpty()) { - fileName = File.separator; + fileName = File.separator } - itemViewHolder.binding.uploadName.setText(fileName); + itemViewHolder.binding.uploadName.text = fileName // remote path to parent folder - itemViewHolder.binding.uploadRemotePath.setText(new File(item.getRemotePath()).getParent()); + itemViewHolder.binding.uploadRemotePath.text = File(item.remotePath).getParent() - long updateTime = item.getUploadEndTimestamp(); + val updateTime = item.uploadEndTimestamp // file size - if (item.getFileSize() != 0) { - String fileSizeFormat = "%s "; + if (item.fileSize != 0L) { + var fileSizeFormat = "%s " // we have valid update time so we can show the upload date if (updateTime > 0) { - fileSizeFormat = "%s, "; + fileSizeFormat = "%s, " } - String fileSizeInBytes = DisplayUtils.bytesToHumanReadable(item.getFileSize()); - String uploadFileSize = String.format(fileSizeFormat, fileSizeInBytes); - itemViewHolder.binding.uploadFileSize.setText(uploadFileSize); + val fileSizeInBytes = DisplayUtils.bytesToHumanReadable(item.fileSize) + val uploadFileSize = String.format(fileSizeFormat, fileSizeInBytes) + itemViewHolder.binding.uploadFileSize.text = uploadFileSize } else { - itemViewHolder.binding.uploadFileSize.setText(""); + itemViewHolder.binding.uploadFileSize.text = "" } // upload date - boolean showUploadDate = (updateTime > 0 && item.getUploadStatus() == UploadStatus.UPLOAD_SUCCEEDED && - item.getLastResult() == UploadResult.UPLOADED); - itemViewHolder.binding.uploadDate.setVisibility(showUploadDate ? View.VISIBLE : View.GONE); + val showUploadDate = + (updateTime > 0 && item.uploadStatus == UploadStatus.UPLOAD_SUCCEEDED && item.lastResult == UploadResult.UPLOADED) + itemViewHolder.binding.uploadDate.visibility = if (showUploadDate) View.VISIBLE else View.GONE if (showUploadDate) { - CharSequence dateString = DisplayUtils.getRelativeDateTimeString(parentActivity, - updateTime, - DateUtils.MINUTE_IN_MILLIS, - DateUtils.WEEK_IN_MILLIS, - 0); - itemViewHolder.binding.uploadDate.setText(dateString); + val dateString = DisplayUtils.getRelativeDateTimeString( + parentActivity, + updateTime, + DateUtils.MINUTE_IN_MILLIS, + DateUtils.WEEK_IN_MILLIS, + 0 + ) + itemViewHolder.binding.uploadDate.text = dateString } // account - final Optional optionalUser = accountManager.getUser(item.getAccountName()); + val optionalUser = accountManager.getUser(item.accountName) if (showUser) { - itemViewHolder.binding.uploadAccount.setVisibility(View.VISIBLE); - if (optionalUser.isPresent()) { - itemViewHolder.binding.uploadAccount.setText( - DisplayUtils.getAccountNameDisplayText(optionalUser.get())); + itemViewHolder.binding.uploadAccount.visibility = View.VISIBLE + if (optionalUser.isPresent) { + itemViewHolder.binding.uploadAccount.text = DisplayUtils.getAccountNameDisplayText(optionalUser.get()) } else { - itemViewHolder.binding.uploadAccount.setText(item.getAccountName()); + itemViewHolder.binding.uploadAccount.text = item.accountName } } else { - itemViewHolder.binding.uploadAccount.setVisibility(View.GONE); + itemViewHolder.binding.uploadAccount.visibility = View.GONE } // Reset fields visibility - itemViewHolder.binding.uploadRemotePath.setVisibility(View.VISIBLE); - itemViewHolder.binding.uploadFileSize.setVisibility(View.VISIBLE); - itemViewHolder.binding.uploadStatus.setVisibility(View.VISIBLE); - itemViewHolder.binding.uploadProgressBar.setVisibility(View.GONE); + itemViewHolder.binding.uploadRemotePath.visibility = View.VISIBLE + itemViewHolder.binding.uploadFileSize.visibility = View.VISIBLE + itemViewHolder.binding.uploadStatus.visibility = View.VISIBLE + itemViewHolder.binding.uploadProgressBar.visibility = View.GONE // Update information depending of upload details - String status = getStatusText(item); - switch (item.getUploadStatus()) { - case UPLOAD_IN_PROGRESS -> { - viewThemeUtils.platform.themeHorizontalProgressBar(itemViewHolder.binding.uploadProgressBar); - itemViewHolder.binding.uploadProgressBar.setProgress(0); - itemViewHolder.binding.uploadProgressBar.setVisibility(View.VISIBLE); + val status = getStatusText(item) + when (item.uploadStatus) { + UploadStatus.UPLOAD_IN_PROGRESS -> { + viewThemeUtils.platform.themeHorizontalProgressBar(itemViewHolder.binding.uploadProgressBar) + itemViewHolder.binding.uploadProgressBar.progress = 0 + itemViewHolder.binding.uploadProgressBar.visibility = View.VISIBLE if (uploadHelper.isUploadingNow(item)) { // really uploading, so... // ... unbind the old progress bar, if any; ... if (uploadProgressListener != null) { - final var upload = uploadProgressListener.getUpload(); + val upload = uploadProgressListener!!.upload if (upload != null) { - String targetKey = FileUploadHelper.Companion.buildRemoteName(upload.getAccountName(), - upload.getRemotePath()); - uploadHelper.removeUploadTransferProgressListener(uploadProgressListener, targetKey); + val targetKey = buildRemoteName( + upload.accountName, + upload.remotePath + ) + uploadHelper.removeUploadTransferProgressListener(uploadProgressListener!!, targetKey) } } // ... then, bind the current progress bar to listen for updates - uploadProgressListener = new UploadProgressListener(item, itemViewHolder.binding.uploadProgressBar); - String targetKey = FileUploadHelper.Companion.buildRemoteName(item.getAccountName(), item.getRemotePath()); - uploadHelper.addUploadTransferProgressListener(uploadProgressListener, targetKey); - + uploadProgressListener = UploadProgressListener(item, itemViewHolder.binding.uploadProgressBar) + val targetKey = buildRemoteName(item.accountName, item.remotePath) + uploadHelper.addUploadTransferProgressListener(uploadProgressListener!!, targetKey) } else { // not really uploading; stop listening progress if view is reused! if (uploadProgressListener != null && - uploadProgressListener.isWrapping(itemViewHolder.binding.uploadProgressBar)) { - final var upload = uploadProgressListener.getUpload(); + uploadProgressListener!!.isWrapping(itemViewHolder.binding.uploadProgressBar) + ) { + val upload = uploadProgressListener!!.upload if (upload != null) { - String targetKey = FileUploadHelper.Companion.buildRemoteName(upload.getAccountName(), - upload.getRemotePath()); - uploadHelper.removeUploadTransferProgressListener(uploadProgressListener, targetKey); - uploadProgressListener = null; + val targetKey = buildRemoteName( + upload.accountName, + upload.remotePath + ) + uploadHelper.removeUploadTransferProgressListener(uploadProgressListener!!, targetKey) + uploadProgressListener = null } } } - itemViewHolder.binding.uploadFileSize.setVisibility(View.GONE); - itemViewHolder.binding.uploadProgressBar.invalidate(); + itemViewHolder.binding.uploadFileSize.visibility = View.GONE + itemViewHolder.binding.uploadProgressBar.invalidate() } - case UPLOAD_FAILED -> { + UploadStatus.UPLOAD_FAILED -> { } - case UPLOAD_SUCCEEDED, UPLOAD_CANCELLED -> - itemViewHolder.binding.uploadStatus.setVisibility(View.GONE); + + UploadStatus.UPLOAD_SUCCEEDED, UploadStatus.UPLOAD_CANCELLED -> itemViewHolder.binding.uploadStatus.visibility = + View.GONE } // show status if same file conflict or local file deleted or upload cancelled - if ((item.getUploadStatus() == UploadStatus.UPLOAD_SUCCEEDED && item.getLastResult() != UploadResult.UPLOADED) - || item.getUploadStatus() == UploadStatus.UPLOAD_CANCELLED) { - - itemViewHolder.binding.uploadStatus.setVisibility(View.VISIBLE); - itemViewHolder.binding.uploadFileSize.setVisibility(View.GONE); + if ((item.uploadStatus == UploadStatus.UPLOAD_SUCCEEDED && item.lastResult != UploadResult.UPLOADED) + || item.uploadStatus == UploadStatus.UPLOAD_CANCELLED + ) { + itemViewHolder.binding.uploadStatus.visibility = View.VISIBLE + itemViewHolder.binding.uploadFileSize.visibility = View.GONE } - itemViewHolder.binding.uploadStatus.setText(status); + itemViewHolder.binding.uploadStatus.text = status // bind listeners to perform actions - if (item.getUploadStatus() == UploadStatus.UPLOAD_IN_PROGRESS) { + if (item.uploadStatus == UploadStatus.UPLOAD_IN_PROGRESS) { // Cancel - itemViewHolder.binding.uploadRightButton.setImageResource(R.drawable.ic_action_cancel_grey); - itemViewHolder.binding.uploadRightButton.setVisibility(View.VISIBLE); - itemViewHolder.binding.uploadRightButton.setOnClickListener(v -> { - uploadHelper.updateUploadStatus(item.getRemotePath(), item.getAccountName(), UploadStatus.UPLOAD_CANCELLED, () -> { - FileUploadWorker.Companion.cancelUpload(item.getRemotePath(), item.getAccountName(), () -> { - loadUploadItemsFromDb(() -> {}); - return Unit.INSTANCE; - }); - return Unit.INSTANCE; - }); - }); - - } else if (item.getUploadStatus() == UploadStatus.UPLOAD_FAILED) { - if (item.getLastResult() == UploadResult.SYNC_CONFLICT) { - itemViewHolder.binding.uploadRightButton.setImageResource(R.drawable.ic_dots_vertical); - itemViewHolder.binding.uploadRightButton.setOnClickListener(view -> optionalUser.ifPresent(user -> showItemConflictPopup(user, itemViewHolder, item, status, view))); + itemViewHolder.binding.uploadRightButton.setImageResource(R.drawable.ic_action_cancel_grey) + itemViewHolder.binding.uploadRightButton.setVisibility(View.VISIBLE) + itemViewHolder.binding.uploadRightButton.setOnClickListener { + uploadHelper.updateUploadStatus( + item.remotePath, + item.accountName, + UploadStatus.UPLOAD_CANCELLED + ) { + cancelUpload(item.remotePath, item.accountName) { + loadUploadItemsFromDb {} + } + } + } + } else if (item.uploadStatus == UploadStatus.UPLOAD_FAILED) { + if (item.lastResult == UploadResult.SYNC_CONFLICT) { + itemViewHolder.binding.uploadRightButton.setImageResource(R.drawable.ic_dots_vertical) + itemViewHolder.binding.uploadRightButton.setOnClickListener { view: View? -> + optionalUser.ifPresent( + Consumer { user: User? -> showItemConflictPopup(user, itemViewHolder, item, status, view) }) + } } else { // Delete - itemViewHolder.binding.uploadRightButton.setImageResource(R.drawable.ic_action_delete_grey); - itemViewHolder.binding.uploadRightButton.setOnClickListener(v -> removeUpload(item)); + itemViewHolder.binding.uploadRightButton.setImageResource(R.drawable.ic_action_delete_grey) + itemViewHolder.binding.uploadRightButton.setOnClickListener { + removeUpload( + item + ) + } } - itemViewHolder.binding.uploadRightButton.setVisibility(View.VISIBLE); + itemViewHolder.binding.uploadRightButton.setVisibility(View.VISIBLE) } else { // UploadStatus.UPLOAD_SUCCEEDED - itemViewHolder.binding.uploadRightButton.setVisibility(View.INVISIBLE); + itemViewHolder.binding.uploadRightButton.setVisibility(View.INVISIBLE) } - itemViewHolder.binding.uploadListItemLayout.setOnClickListener(null); + itemViewHolder.binding.uploadListItemLayout.setOnClickListener(null) // Set icon or thumbnail - itemViewHolder.binding.thumbnail.setImageResource(R.drawable.file); + itemViewHolder.binding.thumbnail.setImageResource(R.drawable.file) // click on item - if (item.getUploadStatus() == UploadStatus.UPLOAD_FAILED || - item.getUploadStatus() == UploadStatus.UPLOAD_CANCELLED) { - - final UploadResult uploadResult = item.getLastResult(); - itemViewHolder.binding.uploadListItemLayout.setOnClickListener(v -> { + if (item.uploadStatus == UploadStatus.UPLOAD_FAILED || + item.uploadStatus == UploadStatus.UPLOAD_CANCELLED + ) { + val uploadResult = item.lastResult + itemViewHolder.binding.uploadListItemLayout.setOnClickListener { v: View? -> if (uploadResult == UploadResult.CREDENTIAL_ERROR) { - final Optional optUser = accountManager.getUser(item.getAccountName()); - final User user = optUser.orElseThrow(RuntimeException::new); - parentActivity.getFileOperationsHelper().checkCurrentCredentials(user); - return; - } else if (uploadResult == UploadResult.SYNC_CONFLICT && optionalUser.isPresent()) { - User user = optionalUser.get(); + val optUser = accountManager.getUser(item.accountName) + val user = optUser.orElseThrow(Supplier { RuntimeException() }) + parentActivity.fileOperationsHelper.checkCurrentCredentials(user) + return@setOnClickListener + } else if (uploadResult == UploadResult.SYNC_CONFLICT && optionalUser.isPresent) { + val user = optionalUser.get() if (checkAndOpenConflictResolutionDialog(user, itemViewHolder, item, status)) { - return; + return@setOnClickListener } } - // not a credentials error - File file = new File(item.getLocalPath()); - Optional user = accountManager.getUser(item.getAccountName()); - if (file.exists() && user.isPresent()) { - uploadHelper.retryUpload(item, user.get()); + val file = File(item.localPath) + val user = accountManager.getUser(item.accountName) + if (file.exists() && user.isPresent) { + uploadHelper.retryUpload(item, user.get()) } else { - DisplayUtils.showSnackMessage(v.getRootView().findViewById(android.R.id.content), R.string.local_file_not_found_message); + DisplayUtils.showSnackMessage( + v!!.getRootView().findViewById(android.R.id.content), + R.string.local_file_not_found_message + ) } - }); - } else if (item.getUploadStatus() == UploadStatus.UPLOAD_SUCCEEDED) { - itemViewHolder.binding.uploadListItemLayout.setOnClickListener(v -> onUploadedItemClick(item)); + } + } else if (item.uploadStatus == UploadStatus.UPLOAD_SUCCEEDED) { + itemViewHolder.binding.uploadListItemLayout.setOnClickListener { + onUploadedItemClick( + item + ) + } } - // click on thumbnail to open locally - if (item.getUploadStatus() != UploadStatus.UPLOAD_SUCCEEDED) { - itemViewHolder.binding.thumbnail.setOnClickListener(v -> onUploadingItemClick(item)); + if (item.uploadStatus != UploadStatus.UPLOAD_SUCCEEDED) { + itemViewHolder.binding.thumbnail.setOnClickListener { + onUploadingItemClick( + item + ) + } } /* * Cancellation needs do be checked and done before changing the drawable in fileIcon, or * {@link ThumbnailsCacheManager#cancelPotentialWork} will NEVER cancel any task. */ - OCFile fakeFileToCheatThumbnailsCacheManagerInterface = new OCFile(item.getRemotePath()); - fakeFileToCheatThumbnailsCacheManagerInterface.setStoragePath(item.getLocalPath()); - fakeFileToCheatThumbnailsCacheManagerInterface.setMimeType(item.getMimeType()); + val fakeFileToCheatThumbnailsCacheManagerInterface = OCFile(item.remotePath) + fakeFileToCheatThumbnailsCacheManagerInterface.setStoragePath(item.localPath) + fakeFileToCheatThumbnailsCacheManagerInterface.mimeType = item.mimeType - boolean allowedToCreateNewThumbnail = ThumbnailsCacheManager.cancelPotentialThumbnailWork( + val allowedToCreateNewThumbnail = ThumbnailsCacheManager.cancelPotentialThumbnailWork( fakeFileToCheatThumbnailsCacheManagerInterface, itemViewHolder.binding.thumbnail - ); + ) // TODO this code is duplicated; refactor to a common place if (MimeTypeUtil.isImage(fakeFileToCheatThumbnailsCacheManagerInterface) - && fakeFileToCheatThumbnailsCacheManagerInterface.getRemoteId() != null && - item.getUploadStatus() == UploadStatus.UPLOAD_SUCCEEDED) { + && fakeFileToCheatThumbnailsCacheManagerInterface.remoteId != null && item.uploadStatus == UploadStatus.UPLOAD_SUCCEEDED + ) { // Thumbnail in Cache? - final var cacheKey = String.valueOf(fakeFileToCheatThumbnailsCacheManagerInterface.getRemoteId()); - Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(cacheKey); + val cacheKey = fakeFileToCheatThumbnailsCacheManagerInterface.remoteId.toString() + var thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(cacheKey) - if (thumbnail != null && !fakeFileToCheatThumbnailsCacheManagerInterface.isUpdateThumbnailNeeded()) { - itemViewHolder.binding.thumbnail.setImageBitmap(thumbnail); + if (thumbnail != null && !fakeFileToCheatThumbnailsCacheManagerInterface.isUpdateThumbnailNeeded) { + itemViewHolder.binding.thumbnail.setImageBitmap(thumbnail) } else { // generate new Thumbnail - Optional user = parentActivity.getUser(); - if (allowedToCreateNewThumbnail && user.isPresent()) { - final ThumbnailsCacheManager.ThumbnailGenerationTask task = - new ThumbnailsCacheManager.ThumbnailGenerationTask( + val user = parentActivity.user + if (allowedToCreateNewThumbnail && user.isPresent) { + val task = + ThumbnailGenerationTask( itemViewHolder.binding.thumbnail, - parentActivity.getStorageManager(), + parentActivity.storageManager, user.get() - ); + ) if (thumbnail == null) { if (MimeTypeUtil.isVideo(fakeFileToCheatThumbnailsCacheManagerInterface)) { - thumbnail = ThumbnailsCacheManager.mDefaultVideo; + thumbnail = ThumbnailsCacheManager.mDefaultVideo } else { - thumbnail = ThumbnailsCacheManager.mDefaultImg; + thumbnail = ThumbnailsCacheManager.mDefaultImg } } - final ThumbnailsCacheManager.AsyncThumbnailDrawable asyncDrawable = - new ThumbnailsCacheManager.AsyncThumbnailDrawable( + val asyncDrawable = + AsyncThumbnailDrawable( parentActivity.getResources(), thumbnail, task - ); - itemViewHolder.binding.thumbnail.setImageDrawable(asyncDrawable); - task.execute(new ThumbnailsCacheManager.ThumbnailGenerationTaskObject( - fakeFileToCheatThumbnailsCacheManagerInterface, null)); + ) + itemViewHolder.binding.thumbnail.setImageDrawable(asyncDrawable) + task.execute( + ThumbnailGenerationTaskObject( + fakeFileToCheatThumbnailsCacheManagerInterface, null + ) + ) } } - if ("image/png".equals(item.getMimeType())) { - final var backgroundColor = ContextCompat.getColor(parentActivity, R.color.bg_default); - itemViewHolder.binding.thumbnail.setBackgroundColor(backgroundColor); + if ("image/png" == item.mimeType) { + val backgroundColor = ContextCompat.getColor(parentActivity, R.color.bg_default) + itemViewHolder.binding.thumbnail.setBackgroundColor(backgroundColor) } } else if (MimeTypeUtil.isImage(fakeFileToCheatThumbnailsCacheManagerInterface)) { - File file = new File(item.getLocalPath()); - Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(String.valueOf(file.hashCode())); + val file = File(item.localPath) + var thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(file.hashCode().toString()) if (thumbnail != null) { - itemViewHolder.binding.thumbnail.setImageBitmap(thumbnail); + itemViewHolder.binding.thumbnail.setImageBitmap(thumbnail) } else if (allowedToCreateNewThumbnail) { - getThumbnailFromFileTypeAndSetIcon(item.getLocalPath(), itemViewHolder); + getThumbnailFromFileTypeAndSetIcon(item.localPath, itemViewHolder) - final ThumbnailsCacheManager.ThumbnailGenerationTask task = - new ThumbnailsCacheManager.ThumbnailGenerationTask(itemViewHolder.binding.thumbnail); + val task = + ThumbnailGenerationTask(itemViewHolder.binding.thumbnail) if (MimeTypeUtil.isVideo(file)) { - thumbnail = ThumbnailsCacheManager.mDefaultVideo; + thumbnail = ThumbnailsCacheManager.mDefaultVideo } else { - thumbnail = ThumbnailsCacheManager.mDefaultImg; + thumbnail = ThumbnailsCacheManager.mDefaultImg } - final ThumbnailsCacheManager.AsyncThumbnailDrawable asyncDrawable = - new ThumbnailsCacheManager.AsyncThumbnailDrawable(parentActivity.getResources(), thumbnail, task); - task.execute(new ThumbnailsCacheManager.ThumbnailGenerationTaskObject(file, null)); - task.setListener(new ThumbnailsCacheManager.ThumbnailGenerationTask.Listener() { - @Override - public void onSuccess() { - itemViewHolder.binding.thumbnail.setImageDrawable(asyncDrawable); + val asyncDrawable = + AsyncThumbnailDrawable(parentActivity.getResources(), thumbnail, task) + task.execute(ThumbnailGenerationTaskObject(file, null)) + task.setListener(object : ThumbnailGenerationTask.Listener { + override fun onSuccess() { + itemViewHolder.binding.thumbnail.setImageDrawable(asyncDrawable) } - @Override - public void onError() { - getThumbnailFromFileTypeAndSetIcon(item.getLocalPath(), itemViewHolder); + override fun onError() { + getThumbnailFromFileTypeAndSetIcon(item.localPath, itemViewHolder) } - }); + }) - Log_OC.v(TAG, "Executing task to generate a new thumbnail"); + Log_OC.v(TAG, "Executing task to generate a new thumbnail") } - if ("image/png".equalsIgnoreCase(item.getMimeType())) { - final var backgroundColor = ContextCompat.getColor(parentActivity, R.color.bg_default); - itemViewHolder.binding.thumbnail.setBackgroundColor(backgroundColor); + if ("image/png".equals(item.mimeType, ignoreCase = true)) { + val backgroundColor = ContextCompat.getColor(parentActivity, R.color.bg_default) + itemViewHolder.binding.thumbnail.setBackgroundColor(backgroundColor) } } else { - if (optionalUser.isPresent()) { - final Drawable icon = MimeTypeUtil.getFileTypeIcon(item.getMimeType(), - fileName, - parentActivity, - viewThemeUtils); - itemViewHolder.binding.thumbnail.setImageDrawable(icon); + if (optionalUser.isPresent) { + val icon = MimeTypeUtil.getFileTypeIcon( + item.mimeType, + fileName, + parentActivity, + viewThemeUtils + ) + itemViewHolder.binding.thumbnail.setImageDrawable(icon) } } } - private void getThumbnailFromFileTypeAndSetIcon(String localPath, ItemViewHolder itemViewHolder) { - Drawable drawable = MimeTypeUtil.getIcon(localPath, parentActivity, viewThemeUtils); - if (drawable == null) return; - itemViewHolder.binding.thumbnail.setImageDrawable(drawable); + private fun getThumbnailFromFileTypeAndSetIcon(localPath: String?, itemViewHolder: ItemViewHolder) { + val drawable = MimeTypeUtil.getIcon(localPath, parentActivity, viewThemeUtils) ?: return + itemViewHolder.binding.thumbnail.setImageDrawable(drawable) } - private boolean checkAndOpenConflictResolutionDialog(User user, - ItemViewHolder itemViewHolder, - OCUpload item, - String status) { - String remotePath = item.getRemotePath(); - OCFile localFile = storageManager.getFileByEncryptedRemotePath(remotePath); + private fun checkAndOpenConflictResolutionDialog( + user: User?, + itemViewHolder: ItemViewHolder, + item: OCUpload, + status: String? + ): Boolean { + val remotePath = item.remotePath + val localFile = storageManager.getFileByEncryptedRemotePath(remotePath) if (localFile == null) { // Remote file doesn't exist, try to refresh folder - OCFile folder = storageManager.getFileByEncryptedRemotePath(new File(remotePath).getParent() + "/"); + val folder = storageManager.getFileByEncryptedRemotePath(File(remotePath).getParent() + "/") - if (folder != null && folder.isFolder()) { - refreshFolderAndUpdateUI(itemViewHolder, user, folder, remotePath, item, status); - return true; + if (folder != null && folder.isFolder) { + refreshFolderAndUpdateUI(itemViewHolder, user, folder, remotePath, item, status) + return true } // Destination folder doesn't exist anymore } if (localFile != null) { - this.openConflictActivity(localFile, item); - return true; + this.openConflictActivity(localFile, item) + return true } // Remote file doesn't exist anymore = there is no more conflict - return false; + return false } - private void refreshFolderAndUpdateUI(ItemViewHolder holder, User user, OCFile folder, String remotePath, - OCUpload item, String status) { - Context context = MainApp.getAppContext(); - - this.refreshFolder(context, holder, user, folder, (caller, result) -> { - holder.binding.uploadStatus.setText(status); - - if (result.isSuccess()) { - OCFile fileOnServer = storageManager.getFileByEncryptedRemotePath(remotePath); + private fun refreshFolderAndUpdateUI( + holder: ItemViewHolder, user: User?, folder: OCFile?, remotePath: String?, + item: OCUpload, status: String? + ) { + val context = MainApp.getAppContext() + + this.refreshFolder( + context, + holder, + user, + folder + ) { _: RemoteOperation<*>?, result: RemoteOperationResult<*>? -> + holder.binding.uploadStatus.text = status + if (result!!.isSuccess) { + val fileOnServer = storageManager.getFileByEncryptedRemotePath(remotePath) if (fileOnServer != null) { - openConflictActivity(fileOnServer, item); + openConflictActivity(fileOnServer, item) } else { - displayFileNotFoundError(holder.itemView, context); + displayFileNotFoundError(holder.itemView, context) } } - }); + } } - private void displayFileNotFoundError(View itemView, Context context) { - String message = context.getString(R.string.uploader_file_not_found_message); - DisplayUtils.showSnackMessage(itemView, message); + private fun displayFileNotFoundError(itemView: View?, context: Context) { + val message = context.getString(R.string.uploader_file_not_found_message) + DisplayUtils.showSnackMessage(itemView, message) } - private void showItemConflictPopup(User user, - ItemViewHolder itemViewHolder, - OCUpload item, - String status, - View view) { - PopupMenu popup = new PopupMenu(MainApp.getAppContext(), view); - popup.inflate(R.menu.upload_list_item_file_conflict); - popup.setOnMenuItemClickListener(i -> { - int itemId = i.getItemId(); - + private fun showItemConflictPopup( + user: User?, + itemViewHolder: ItemViewHolder, + item: OCUpload, + status: String?, + view: View? + ) { + val popup = PopupMenu(MainApp.getAppContext(), view) + popup.inflate(R.menu.upload_list_item_file_conflict) + popup.setOnMenuItemClickListener { i: MenuItem? -> + val itemId = i!!.itemId if (itemId == R.id.action_upload_list_resolve_conflict) { - checkAndOpenConflictResolutionDialog(user, itemViewHolder, item, status); + checkAndOpenConflictResolutionDialog(user, itemViewHolder, item, status) } else { - removeUpload(item); + removeUpload(item) } - - return true; - }); - popup.show(); + true + } + popup.show() } - public void removeUpload(OCUpload item) { - uploadsStorageManager.removeUpload(item); - cancelOldErrorNotification(item); - loadUploadItemsFromDb(() -> {}); + fun removeUpload(item: OCUpload?) { + uploadsStorageManager.removeUpload(item) + cancelOldErrorNotification(item) + loadUploadItemsFromDb {} } - private void refreshFolder( - Context context, - ItemViewHolder view, - User user, - OCFile folder, - OnRemoteOperationListener listener) { - view.binding.uploadListItemLayout.setClickable(false); - view.binding.uploadStatus.setText(R.string.uploads_view_upload_status_fetching_server_version); - new RefreshFolderOperation(folder, - clock.getCurrentTime(), - false, - false, - true, - storageManager, - user, - context) - .execute(user, context, (caller, result) -> { - view.binding.uploadListItemLayout.setClickable(true); - listener.onRemoteOperationFinish(caller, result); - }, parentActivity.getHandler()); + private fun refreshFolder( + context: Context?, + view: ItemViewHolder, + user: User?, + folder: OCFile?, + listener: OnRemoteOperationListener + ) { + view.binding.uploadListItemLayout.isClickable = false + view.binding.uploadStatus.setText(R.string.uploads_view_upload_status_fetching_server_version) + RefreshFolderOperation( + folder, + clock.currentTime, + false, + false, + true, + storageManager, + user, + context + ) + .execute( + user, + context, + { caller: RemoteOperation<*>?, result: RemoteOperationResult<*>? -> + view.binding.uploadListItemLayout.isClickable = true + listener.onRemoteOperationFinish(caller, result) + }, + parentActivity.handler + ) } - private void openConflictActivity(OCFile file, OCUpload upload) { - file.setStoragePath(upload.getLocalPath()); - - Context context = MainApp.getAppContext(); - Optional user = accountManager.getUser(upload.getAccountName()); - if (user.isPresent()) { - Intent intent = ConflictsResolveActivity.createIntent(file, - user.get(), - upload.getUploadId(), - Intent.FLAG_ACTIVITY_NEW_TASK, - context); - - context.startActivity(intent); + private fun openConflictActivity(file: OCFile, upload: OCUpload) { + file.setStoragePath(upload.localPath) + + val context = MainApp.getAppContext() + val user = accountManager.getUser(upload.accountName) + if (user.isPresent) { + val intent = createIntent( + file, + user.get(), + upload.uploadId, + Intent.FLAG_ACTIVITY_NEW_TASK, + context + ) + + context.startActivity(intent) } } /** * Gets the status text to show to the user according to the status and last result of the the given upload. - * + * * @param upload Upload to describe. * @return Text describing the status of the given upload. */ - private String getStatusText(OCUpload upload) { - String status; - var statusRes = parentActivity.getResources(); - var prefs = parentActivity.getAppPreferences(); - var uploadStatus = upload.getUploadStatus(); - - switch (uploadStatus) { - case UPLOAD_IN_PROGRESS -> { - if (prefs.isGlobalUploadPaused()) { - status = statusRes.getString(R.string.upload_global_pause_title); + private fun getStatusText(upload: OCUpload): String { + val status: String + val statusRes = parentActivity.getResources() + val prefs = parentActivity.appPreferences + val uploadStatus = upload.uploadStatus + + when (uploadStatus) { + UploadStatus.UPLOAD_IN_PROGRESS -> { + status = if (prefs.isGlobalUploadPaused()) { + statusRes.getString(R.string.upload_global_pause_title) } else if (uploadHelper.isUploadingNow(upload)) { - status = statusRes.getString(R.string.uploader_upload_in_progress_ticker); + statusRes.getString(R.string.uploader_upload_in_progress_ticker) } else { - status = statusRes.getString(R.string.uploads_view_later_waiting_to_upload); + statusRes.getString(R.string.uploads_view_later_waiting_to_upload) } } - case UPLOAD_SUCCEEDED -> { - UploadResult result = upload.getLastResult(); - if (result == UploadResult.SAME_FILE_CONFLICT) { - status = statusRes.getString(R.string.uploads_view_upload_status_succeeded_same_file); + UploadStatus.UPLOAD_SUCCEEDED -> { + val result = upload.lastResult + status = if (result == UploadResult.SAME_FILE_CONFLICT) { + statusRes.getString(R.string.uploads_view_upload_status_succeeded_same_file) } else if (result == UploadResult.FILE_NOT_FOUND) { - status = getUploadFailedStatusText(result); - } else if (upload.getNameCollisionPolicy() == NameCollisionPolicy.SKIP) { - status = statusRes.getString(R.string.uploads_view_upload_status_skip_reason); + getUploadFailedStatusText(result) + } else if (upload.nameCollisionPolicy == NameCollisionPolicy.SKIP) { + statusRes.getString(R.string.uploads_view_upload_status_skip_reason) } else { - status = statusRes.getString(R.string.uploads_view_upload_status_succeeded); + statusRes.getString(R.string.uploads_view_upload_status_succeeded) } } - case UPLOAD_FAILED -> - status = getUploadFailedStatusText(upload.getLastResult()); - - case UPLOAD_CANCELLED -> - status = statusRes.getString(R.string.upload_manually_cancelled); - - default -> - status = "Uncontrolled status: " + uploadStatus; + UploadStatus.UPLOAD_FAILED -> status = getUploadFailedStatusText(upload.lastResult) + UploadStatus.UPLOAD_CANCELLED -> status = statusRes.getString(R.string.upload_manually_cancelled) + else -> status = "Uncontrolled status: $uploadStatus" } - return status; + return status } - - @NonNull - private String getUploadFailedStatusText(UploadResult result) { - return switch (result) { - case CREDENTIAL_ERROR -> - parentActivity.getString(R.string.uploads_view_upload_status_failed_credentials_error); - case FOLDER_ERROR -> parentActivity.getString(R.string.uploads_view_upload_status_failed_folder_error); - case FILE_NOT_FOUND -> parentActivity.getString(R.string.uploads_view_upload_status_failed_localfile_error); - case FILE_ERROR -> parentActivity.getString(R.string.uploads_view_upload_status_failed_file_error); - case PRIVILEGES_ERROR -> - parentActivity.getString(R.string.uploads_view_upload_status_failed_permission_error); - case NETWORK_CONNECTION -> - parentActivity.getString(R.string.uploads_view_upload_status_failed_connection_error); - case DELAYED_FOR_WIFI -> parentActivity.getString(R.string.uploads_view_upload_status_waiting_for_wifi); - case DELAYED_FOR_CHARGING -> - parentActivity.getString(R.string.uploads_view_upload_status_waiting_for_charging); - case CONFLICT_ERROR -> parentActivity.getString(R.string.uploads_view_upload_status_conflict); - case SERVICE_INTERRUPTED -> - parentActivity.getString(R.string.uploads_view_upload_status_service_interrupted); - case CANCELLED -> - // should not get here ; cancelled uploads should be wiped out - parentActivity.getString(R.string.uploads_view_upload_status_cancelled); - case UPLOADED -> - // should not get here ; status should be UPLOAD_SUCCESS - parentActivity.getString(R.string.uploads_view_upload_status_succeeded); - case MAINTENANCE_MODE -> parentActivity.getString(R.string.maintenance_mode); - case SSL_RECOVERABLE_PEER_UNVERIFIED -> parentActivity.getString( + private fun getUploadFailedStatusText(result: UploadResult): String { + return when (result) { + UploadResult.CREDENTIAL_ERROR -> parentActivity.getString(R.string.uploads_view_upload_status_failed_credentials_error) + UploadResult.FOLDER_ERROR -> parentActivity.getString(R.string.uploads_view_upload_status_failed_folder_error) + UploadResult.FILE_NOT_FOUND -> parentActivity.getString(R.string.uploads_view_upload_status_failed_localfile_error) + UploadResult.FILE_ERROR -> parentActivity.getString(R.string.uploads_view_upload_status_failed_file_error) + UploadResult.PRIVILEGES_ERROR -> parentActivity.getString(R.string.uploads_view_upload_status_failed_permission_error) + UploadResult.NETWORK_CONNECTION -> parentActivity.getString(R.string.uploads_view_upload_status_failed_connection_error) + UploadResult.DELAYED_FOR_WIFI -> parentActivity.getString(R.string.uploads_view_upload_status_waiting_for_wifi) + UploadResult.DELAYED_FOR_CHARGING -> parentActivity.getString(R.string.uploads_view_upload_status_waiting_for_charging) + UploadResult.CONFLICT_ERROR -> parentActivity.getString(R.string.uploads_view_upload_status_conflict) + UploadResult.SERVICE_INTERRUPTED -> parentActivity.getString(R.string.uploads_view_upload_status_service_interrupted) + UploadResult.CANCELLED -> // should not get here ; cancelled uploads should be wiped out + parentActivity.getString(R.string.uploads_view_upload_status_cancelled) + + UploadResult.UPLOADED -> // should not get here ; status should be UPLOAD_SUCCESS + parentActivity.getString(R.string.uploads_view_upload_status_succeeded) + + UploadResult.MAINTENANCE_MODE -> parentActivity.getString(R.string.maintenance_mode) + UploadResult.SSL_RECOVERABLE_PEER_UNVERIFIED -> parentActivity.getString( R.string.uploads_view_upload_status_failed_ssl_certificate_not_trusted - ); - case UNKNOWN -> parentActivity.getString(R.string.uploads_view_upload_status_unknown_fail); - case LOCK_FAILED -> parentActivity.getString(R.string.upload_lock_failed); - case DELAYED_IN_POWER_SAVE_MODE -> parentActivity.getString( - R.string.uploads_view_upload_status_waiting_exit_power_save_mode); - case VIRUS_DETECTED -> parentActivity.getString(R.string.uploads_view_upload_status_virus_detected); - case LOCAL_STORAGE_FULL -> parentActivity.getString(R.string.upload_local_storage_full); - case OLD_ANDROID_API -> parentActivity.getString(R.string.upload_old_android); - case SYNC_CONFLICT -> parentActivity.getString(R.string.upload_sync_conflict); - case CANNOT_CREATE_FILE -> parentActivity.getString(R.string.upload_cannot_create_file); - case LOCAL_STORAGE_NOT_COPIED -> parentActivity.getString(R.string.upload_local_storage_not_copied); - case QUOTA_EXCEEDED -> parentActivity.getString(R.string.upload_quota_exceeded); - default -> parentActivity.getString(R.string.upload_unknown_error); - }; + ) + + UploadResult.UNKNOWN -> parentActivity.getString(R.string.uploads_view_upload_status_unknown_fail) + UploadResult.LOCK_FAILED -> parentActivity.getString(R.string.upload_lock_failed) + UploadResult.DELAYED_IN_POWER_SAVE_MODE -> parentActivity.getString( + R.string.uploads_view_upload_status_waiting_exit_power_save_mode + ) + + UploadResult.VIRUS_DETECTED -> parentActivity.getString(R.string.uploads_view_upload_status_virus_detected) + UploadResult.LOCAL_STORAGE_FULL -> parentActivity.getString(R.string.upload_local_storage_full) + UploadResult.OLD_ANDROID_API -> parentActivity.getString(R.string.upload_old_android) + UploadResult.SYNC_CONFLICT -> parentActivity.getString(R.string.upload_sync_conflict) + UploadResult.CANNOT_CREATE_FILE -> parentActivity.getString(R.string.upload_cannot_create_file) + UploadResult.LOCAL_STORAGE_NOT_COPIED -> parentActivity.getString(R.string.upload_local_storage_not_copied) + UploadResult.QUOTA_EXCEEDED -> parentActivity.getString(R.string.upload_quota_exceeded) + else -> parentActivity.getString(R.string.upload_unknown_error) + } } - @Override - @NonNull - public SectionedViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - if (viewType == VIEW_TYPE_HEADER) { - return new HeaderViewHolder( - UploadListHeaderBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false) - ); + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SectionedViewHolder { + return if (viewType == VIEW_TYPE_HEADER) { + HeaderViewHolder( + UploadListHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) } else { - return new ItemViewHolder( - UploadListItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false) - ); + ItemViewHolder( + UploadListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) } } @SuppressLint("NotifyDataSetChanged") - public final void loadUploadItemsFromDb(Runnable onCompleted) { - parentActivity.getUser().ifPresent(user -> { - String accountName = user.getAccountName(); - final var optionalCapabilities = parentActivity.getCapabilities(); - if (optionalCapabilities.isPresent()) { - final var capabilities = optionalCapabilities.get(); - for (int i = 0; i < sections.size(); i++) { - final int index = i; - Section sec = sections.get(index); - - uploadHelper.getUploadsByStatus(accountName, - sec.status(), - capabilities, - sec.collisionPolicy(), - uploads -> { - for (OCUpload upload : uploads) { - upload.setDataFixed(uploadHelper); - } - Arrays.sort(uploads, new OCUploadComparator()); - - sections.set(index, sec.withItems(uploads)); - - parentActivity.runOnUiThread(() -> { - notifyDataSetChanged(); - onCompleted.run(); - }); - return Unit.INSTANCE; - }); + fun loadUploadItemsFromDb(onCompleted: Runnable) { + parentActivity.user.ifPresent { user -> + val accountName = user.accountName + val optionalCapabilities = parentActivity.capabilities + if (!optionalCapabilities.isPresent) return@ifPresent + val capabilities = optionalCapabilities.get() + + sections.indices.forEach { i -> + val sec = sections[i] + uploadHelper.getUploadsByStatus(accountName, sec.status!!, capabilities, sec.collisionPolicy) { uploads -> + uploads.forEach { it.setDataFixed(uploadHelper) } + sections[i] = sec.withItems(uploads.sortedByUploadOrder()) + parentActivity.runOnUiThread { + notifyDataSetChanged() + onCompleted.run() + } } } - }); + } } /** * Open local file. */ - private void onUploadingItemClick(OCUpload file) { - File f = new File(file.getLocalPath()); + private fun onUploadingItemClick(file: OCUpload) { + val f = File(file.localPath) if (!f.exists()) { - DisplayUtils.showSnackMessage(parentActivity, R.string.local_file_not_found_message); + DisplayUtils.showSnackMessage(parentActivity, R.string.local_file_not_found_message) } else { - openFileWithDefault(file.getLocalPath()); + openFileWithDefault(file.localPath) } } /** * Open remote file. */ - private void onUploadedItemClick(OCUpload upload) { - final OCFile file = parentActivity.getStorageManager().getFileByEncryptedRemotePath(upload.getRemotePath()); + private fun onUploadedItemClick(upload: OCUpload) { + val file = parentActivity.storageManager.getFileByEncryptedRemotePath(upload.remotePath) if (file == null) { - DisplayUtils.showSnackMessage(parentActivity, R.string.error_retrieving_file); - Log_OC.i(TAG, "Could not find uploaded file on remote."); - return; + DisplayUtils.showSnackMessage(parentActivity, R.string.error_retrieving_file) + Log_OC.i(TAG, "Could not find uploaded file on remote.") + return } - final var optionalUser = parentActivity.getUser(); + val optionalUser = parentActivity.user - if (PreviewImageFragment.canBePreviewed(file) && optionalUser.isPresent()) { + if (canBePreviewed(file) && optionalUser.isPresent) { //show image preview and stay in uploads tab - Intent intent = FileDisplayActivity.openFileIntent(parentActivity, optionalUser.get(), file); - parentActivity.startActivity(intent); + val intent = openFileIntent(parentActivity, optionalUser.get(), file) + parentActivity.startActivity(intent) } else { - Intent intent = new Intent(parentActivity, FileDisplayActivity.class); - intent.setAction(Intent.ACTION_VIEW); - intent.putExtra(FileDisplayActivity.KEY_FILE_PATH, upload.getRemotePath()); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - parentActivity.startActivity(intent); + val intent = Intent(parentActivity, FileDisplayActivity::class.java) + intent.setAction(Intent.ACTION_VIEW) + intent.putExtra(FileDisplayActivity.KEY_FILE_PATH, upload.remotePath) + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + parentActivity.startActivity(intent) } } - /** * Open file with app associates with its MIME type. If MIME type unknown, show list with all apps. */ - private void openFileWithDefault(String localPath) { - Intent myIntent = new Intent(Intent.ACTION_VIEW); - File file = new File(localPath); - String mimetype = MimeTypeUtil.getBestMimeTypeByFilename(localPath); - if ("application/octet-stream".equals(mimetype)) { - mimetype = "*/*"; - } - myIntent.setDataAndType(Uri.fromFile(file), mimetype); - try { - parentActivity.startActivity(myIntent); - } catch (ActivityNotFoundException e) { - DisplayUtils.showSnackMessage(parentActivity, R.string.file_list_no_app_for_file_type); - Log_OC.i(TAG, "Could not find app for sending log history."); + private fun openFileWithDefault(localPath: String) { + val myIntent = Intent(Intent.ACTION_VIEW) + val file = File(localPath) + var mimetype = MimeTypeUtil.getBestMimeTypeByFilename(localPath) + if ("application/octet-stream" == mimetype) { + mimetype = "*/*" } - } - - static class HeaderViewHolder extends SectionedViewHolder { - UploadListHeaderBinding binding; - - HeaderViewHolder(UploadListHeaderBinding binding) { - super(binding.getRoot()); - this.binding = binding; + myIntent.setDataAndType(Uri.fromFile(file), mimetype) + try { + parentActivity.startActivity(myIntent) + } catch (e: ActivityNotFoundException) { + DisplayUtils.showSnackMessage(parentActivity, R.string.file_list_no_app_for_file_type) + Log_OC.i(TAG, "Could not find app for sending log history: $e") } } - static class ItemViewHolder extends SectionedViewHolder { - UploadListItemBinding binding; + internal class HeaderViewHolder(var binding: UploadListHeaderBinding) : SectionedViewHolder( + binding.getRoot() + ) - ItemViewHolder(UploadListItemBinding binding) { - super(binding.getRoot()); - this.binding = binding; - } - } + internal class ItemViewHolder(var binding: UploadListItemBinding) : SectionedViewHolder( + binding.getRoot() + ) - enum Type { + internal enum class Type { CURRENT, COMPLETED, FAILED, CANCELLED, SKIPPED } - public void cancelOldErrorNotification(OCUpload upload) { + fun cancelOldErrorNotification(upload: OCUpload?) { if (mNotificationManager == null) { - mNotificationManager = (NotificationManager) parentActivity.getSystemService(Context.NOTIFICATION_SERVICE); + mNotificationManager = parentActivity.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager? } if (upload == null) { - return; + return } - mNotificationManager.cancel((int) upload.getUploadId()); + mNotificationManager!!.cancel(upload.uploadId.toInt()) + } + + companion object { + private val TAG: String = UploadListAdapter::class.java.getSimpleName() } } diff --git a/app/src/test/java/com/owncloud/android/ui/db/OCUploadComparatorTest.kt b/app/src/test/java/com/owncloud/android/ui/db/OCUploadComparatorTest.kt index 6c18ffc3327f..f9bcd63c83bf 100644 --- a/app/src/test/java/com/owncloud/android/ui/db/OCUploadComparatorTest.kt +++ b/app/src/test/java/com/owncloud/android/ui/db/OCUploadComparatorTest.kt @@ -1,178 +1,114 @@ /* * Nextcloud - Android Client * - * SPDX-FileCopyrightText: 2019 Daniele Fognini - * SPDX-License-Identifier: GPL-3.0-or-later + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later */ package com.owncloud.android.ui.db +import com.nextcloud.utils.extensions.sortedByUploadOrder import com.owncloud.android.datamodel.UploadsStorageManager.UploadStatus.UPLOAD_FAILED import com.owncloud.android.datamodel.UploadsStorageManager.UploadStatus.UPLOAD_IN_PROGRESS import com.owncloud.android.db.OCUpload -import com.owncloud.android.db.OCUploadComparator -import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertEquals import org.junit.BeforeClass import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import org.junit.runners.Suite import org.mockito.MockitoAnnotations import org.mockito.kotlin.mock import org.mockito.kotlin.whenever -@RunWith(Suite::class) -@Suite.SuiteClasses( - OCUploadComparatorTest.Ordering::class, - OCUploadComparatorTest.ComparatorContract::class -) -class OCUploadComparatorTest { - - internal abstract class Base { - companion object { - val failed = mock(name = "failed") - val failedLater = mock(name = "failedLater") - val failedSameTimeOtherId = mock(name = "failedSameTimeOtherId") - val equalsNotSame = mock(name = "equalsNotSame") - val inProgress = mock(name = "InProgress") - val inProgressNow = mock(name = "inProgressNow") - private const val FIXED_UPLOAD_END_TIMESTAMP = 42L - private const val FIXED_UPLOAD_END_TIMESTAMP_LATER = 43L - private const val UPLOAD_ID = 40L - private const val UPLOAD_ID2 = 43L - - fun uploads(): Array = - arrayOf(failed, failedLater, inProgress, inProgressNow, failedSameTimeOtherId) - - @JvmStatic - @BeforeClass - fun setupMocks() { - MockitoAnnotations.initMocks(this) - - whenever(failed.fixedUploadStatus).thenReturn(UPLOAD_FAILED) - whenever(inProgress.fixedUploadStatus).thenReturn(UPLOAD_IN_PROGRESS) - whenever(inProgressNow.fixedUploadStatus).thenReturn(UPLOAD_IN_PROGRESS) - whenever(failedLater.fixedUploadStatus).thenReturn(UPLOAD_FAILED) - whenever(failedSameTimeOtherId.fixedUploadStatus).thenReturn(UPLOAD_FAILED) - whenever(equalsNotSame.fixedUploadStatus).thenReturn(UPLOAD_FAILED) - - whenever(inProgressNow.isFixedUploadingNow).thenReturn(true) - whenever(inProgress.isFixedUploadingNow).thenReturn(false) - - whenever(failed.fixedUploadEndTimeStamp).thenReturn(FIXED_UPLOAD_END_TIMESTAMP) - whenever(failedLater.fixedUploadEndTimeStamp).thenReturn(FIXED_UPLOAD_END_TIMESTAMP_LATER) - whenever(failedSameTimeOtherId.fixedUploadEndTimeStamp).thenReturn(FIXED_UPLOAD_END_TIMESTAMP) - whenever(equalsNotSame.fixedUploadEndTimeStamp).thenReturn(FIXED_UPLOAD_END_TIMESTAMP) - - whenever(failedLater.uploadId).thenReturn(UPLOAD_ID2) - whenever(failedSameTimeOtherId.uploadId).thenReturn(UPLOAD_ID) - whenever(equalsNotSame.uploadId).thenReturn(UPLOAD_ID) - } +class OCUploadSortingTest { + + companion object { + val failed = mock(name = "failed") + val failedLater = mock(name = "failedLater") + val failedSameTimeOtherId = mock(name = "failedSameTimeOtherId") + val equalsNotSame = mock(name = "equalsNotSame") + val inProgress = mock(name = "inProgress") + val inProgressNow = mock(name = "inProgressNow") + + private const val FIXED_UPLOAD_END_TIMESTAMP = 42L + private const val FIXED_UPLOAD_END_TIMESTAMP_LATER = 43L + private const val UPLOAD_ID = 40L + private const val UPLOAD_ID2 = 43L + + @JvmStatic + @BeforeClass + fun setupMocks() { + MockitoAnnotations.openMocks(this) + + whenever(failed.fixedUploadStatus).thenReturn(UPLOAD_FAILED) + whenever(inProgress.fixedUploadStatus).thenReturn(UPLOAD_IN_PROGRESS) + whenever(inProgressNow.fixedUploadStatus).thenReturn(UPLOAD_IN_PROGRESS) + whenever(failedLater.fixedUploadStatus).thenReturn(UPLOAD_FAILED) + whenever(failedSameTimeOtherId.fixedUploadStatus).thenReturn(UPLOAD_FAILED) + whenever(equalsNotSame.fixedUploadStatus).thenReturn(UPLOAD_FAILED) + + whenever(inProgressNow.isFixedUploadingNow).thenReturn(true) + whenever(inProgress.isFixedUploadingNow).thenReturn(false) + + whenever(failed.fixedUploadEndTimeStamp).thenReturn(FIXED_UPLOAD_END_TIMESTAMP) + whenever(failedLater.fixedUploadEndTimeStamp).thenReturn(FIXED_UPLOAD_END_TIMESTAMP_LATER) + whenever(failedSameTimeOtherId.fixedUploadEndTimeStamp).thenReturn(FIXED_UPLOAD_END_TIMESTAMP) + whenever(equalsNotSame.fixedUploadEndTimeStamp).thenReturn(FIXED_UPLOAD_END_TIMESTAMP) + + whenever(failedLater.fixedUploadId).thenReturn(UPLOAD_ID2) + whenever(failedSameTimeOtherId.fixedUploadId).thenReturn(UPLOAD_ID) + whenever(equalsNotSame.fixedUploadId).thenReturn(UPLOAD_ID) } } - internal class Ordering : Base() { - - @Test - fun `same are compared equals in the list`() { - assertEquals(0, OCUploadComparator().compare(failed, failed)) - } - - @Test - fun `in progress is before failed in the list`() { - assertEquals(1, OCUploadComparator().compare(failed, inProgress)) - } + @Test + fun `in progress comes before failed`() { + val result = listOf(failed, inProgress).sortedByUploadOrder() + assertEquals(listOf(inProgress, failed), result) + } - @Test - fun `in progress uploading now is before in progress in the list`() { - assertEquals(1, OCUploadComparator().compare(inProgress, inProgressNow)) - } + @Test + fun `uploading now comes before not uploading`() { + val result = listOf(inProgress, inProgressNow).sortedByUploadOrder() + assertEquals(listOf(inProgressNow, inProgress), result) + } - @Test - fun `later upload end is earlier in the list`() { - assertEquals(1, OCUploadComparator().compare(failed, failedLater)) - } + @Test + fun `later upload end comes first`() { + val result = listOf(failed, failedLater).sortedByUploadOrder() + assertEquals(listOf(failedLater, failed), result) + } - @Test - fun `smaller upload id is earlier in the list`() { - assertEquals(1, OCUploadComparator().compare(failed, failedLater)) - } + @Test + fun `smaller upload id comes later when others equal`() { + val result = listOf(failedLater, failedSameTimeOtherId).sortedByUploadOrder() + assertEquals(listOf(failedLater, failedSameTimeOtherId), result) + } - @Test - fun `same parameters compare equal in the list`() { - assertEquals(0, OCUploadComparator().compare(failedSameTimeOtherId, equalsNotSame)) - } + @Test + fun `same parameters keep stable ordering`() { + val result = listOf(failedSameTimeOtherId, equalsNotSame).sortedByUploadOrder() + assertEquals(listOf(failedSameTimeOtherId, equalsNotSame), result) + } - @Test - fun `sort some uploads in the list`() { - val array = arrayOf( - inProgress, + @Test + fun `sort full list`() { + val result = listOf( + inProgress, + inProgressNow, + failedSameTimeOtherId, + inProgressNow, + failedLater, + failed + ).sortedByUploadOrder() + + assertEquals( + listOf( inProgressNow, - failedSameTimeOtherId, inProgressNow, - null, + inProgress, failedLater, + failedSameTimeOtherId, failed - ) - - array.sortWith(OCUploadComparator()) - - assertArrayEquals( - arrayOf( - null, - inProgressNow, - inProgressNow, - inProgress, - failedLater, - failedSameTimeOtherId, - failed - ), - array - ) - } - } - - @RunWith(Parameterized::class) - internal class ComparatorContract( - private val upload1: OCUpload, - private val upload2: OCUpload, - private val upload3: OCUpload - ) : Base() { - companion object { - @JvmStatic - @Parameterized.Parameters(name = "{0}, {1}, {2}") - fun data(): List> = uploads().flatMap { u1 -> - uploads().flatMap { u2 -> - uploads().map { u3 -> - arrayOf(u1, u2, u3) - } - } - } - } - - @Test - fun `comparator is reflective`() { - assertEquals( - -OCUploadComparator().compare(upload1, upload2), - OCUploadComparator().compare(upload2, upload1) - ) - } - - @Test - fun `comparator is compatible with equals`() { - if (upload1 == upload2) { - assertEquals(0, OCUploadComparator().compare(upload1, upload2)) - } - } - - @Test - fun `comparator is transitive`() { - val compare12 = OCUploadComparator().compare(upload1, upload2) - val compare23 = OCUploadComparator().compare(upload2, upload3) - - if (compare12 == compare23) { - assertEquals(compare12, OCUploadComparator().compare(upload1, upload3)) - } - } + ), + result + ) } } From 24c844d1132fe5ee6bfdf7751a3a314c2c5e5427 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 27 Mar 2026 15:10:35 +0100 Subject: [PATCH 03/11] refactor(upload-list-adapter): separate header section into functions Signed-off-by: alperozturk96 --- .../android/ui/adapter/UploadListAdapter.kt | 259 ++++++++---------- 1 file changed, 116 insertions(+), 143 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.kt index 6380fc17cc7f..f0d70998a5cf 100755 --- a/app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.kt @@ -19,6 +19,7 @@ import android.view.View import android.view.ViewGroup import android.widget.PopupMenu import androidx.core.content.ContextCompat +import androidx.lifecycle.lifecycleScope import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter import com.afollestad.sectionedrecyclerview.SectionedViewHolder import com.nextcloud.client.account.User @@ -60,19 +61,22 @@ import com.owncloud.android.ui.preview.PreviewImageFragment.Companion.canBePrevi import com.owncloud.android.utils.DisplayUtils import com.owncloud.android.utils.MimeTypeUtil import com.owncloud.android.utils.theme.ViewThemeUtils +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.io.File import java.util.function.Consumer import java.util.function.Supplier class UploadListAdapter( - fileActivity: FileActivity, - uploadsStorageManager: UploadsStorageManager, - storageManager: FileDataStorageManager, - accountManager: UserAccountManager, - connectivityService: ConnectivityService, - powerManagementService: PowerManagementService, - clock: Clock, - viewThemeUtils: ViewThemeUtils + private val fileActivity: FileActivity, + private val uploadsStorageManager: UploadsStorageManager, + private val storageManager: FileDataStorageManager, + private val accountManager: UserAccountManager, + private val connectivityService: ConnectivityService, + private val powerManagementService: PowerManagementService, + private val clock: Clock, + private val viewThemeUtils: ViewThemeUtils ) : SectionedRecyclerViewAdapter() { private data class Section( @@ -85,6 +89,14 @@ class UploadListAdapter( fun withItems(newItems: List) = copy(items = newItems) } + internal enum class Type { CURRENT, COMPLETED, FAILED, CANCELLED, SKIPPED } + + internal class HeaderViewHolder(val binding: UploadListHeaderBinding) : + SectionedViewHolder(binding.root) + + internal class ItemViewHolder(val binding: UploadListItemBinding) : + SectionedViewHolder(binding.root) + private val sections: MutableList
= ArrayList
( listOf( Section( @@ -125,32 +137,15 @@ class UploadListAdapter( ) ) + private val parentActivity: FileActivity = fileActivity + private val showUser: Boolean = accountManager.getAccounts().size > 1 + private val uploadHelper = instance() private var uploadProgressListener: UploadProgressListener? = null - private val parentActivity: FileActivity - private val uploadsStorageManager: UploadsStorageManager - private val storageManager: FileDataStorageManager - private val connectivityService: ConnectivityService - private val powerManagementService: PowerManagementService - private val accountManager: UserAccountManager - private val clock: Clock - private val showUser: Boolean - private val viewThemeUtils: ViewThemeUtils private var mNotificationManager: NotificationManager? = null - private val uploadHelper = instance() init { Log_OC.d(TAG, "UploadListAdapter") - - this.parentActivity = fileActivity - this.uploadsStorageManager = uploadsStorageManager - this.storageManager = storageManager - this.accountManager = accountManager - this.connectivityService = connectivityService - this.powerManagementService = powerManagementService - this.clock = clock - this.viewThemeUtils = viewThemeUtils shouldShowHeadersForEmptySections(false) - showUser = accountManager.getAccounts().size > 1 } override fun getSectionCount(): Int { @@ -161,122 +156,113 @@ class UploadListAdapter( return sections[section].items.size } + // region header override fun onBindHeaderViewHolder(holder: SectionedViewHolder?, section: Int, expanded: Boolean) { val headerViewHolder = holder as HeaderViewHolder - val group = sections[section] - val title = parentActivity.getString(group.titleRes) - val count = group.items.size - headerViewHolder.binding.uploadListTitle.text = - String.format(parentActivity.getString(R.string.uploads_view_group_header), title, count) - viewThemeUtils.platform.colorTextView(headerViewHolder.binding.uploadListTitle) + bindHeaderTitle(headerViewHolder, group, section) + bindHeaderActionButton(headerViewHolder, group) + bindHeaderBatterySaverWarning(headerViewHolder) + bindHeaderActionClickListener(headerViewHolder, group) + } - headerViewHolder.binding.uploadListTitle.setOnClickListener { - toggleSectionExpanded(section) - val icon = if (isSectionExpanded(section)) R.drawable.ic_expand_less else R.drawable.ic_expand_more - headerViewHolder.binding.uploadListState.setImageResource(icon) - } + private fun bindHeaderTitle(holder: HeaderViewHolder, group: Section, section: Int) { + val title = parentActivity.getString(group.titleRes) + val headerText = parentActivity.getString(R.string.uploads_view_group_header) + holder.binding.uploadListTitle.text = String.format(headerText, title, group.items.size) + viewThemeUtils.platform.colorTextView(holder.binding.uploadListTitle) - headerViewHolder.binding.uploadListStateLayout.setOnClickListener { + val toggleExpand = { toggleSectionExpanded(section) val icon = if (isSectionExpanded(section)) R.drawable.ic_expand_less else R.drawable.ic_expand_more - headerViewHolder.binding.uploadListState.setImageResource(icon) + holder.binding.uploadListState.setImageResource(icon) } + holder.binding.uploadListTitle.setOnClickListener { toggleExpand() } + holder.binding.uploadListStateLayout.setOnClickListener { toggleExpand() } + } - when (group.type) { - Type.CURRENT, Type.COMPLETED -> headerViewHolder.binding.uploadListAction.setImageResource(R.drawable.ic_close) - Type.CANCELLED, Type.FAILED -> headerViewHolder.binding.uploadListAction.setImageResource(R.drawable.ic_dots_vertical) - else -> {} + private fun bindHeaderActionButton(holder: HeaderViewHolder, group: Section) { + val iconRes = when (group.type) { + Type.CURRENT, Type.COMPLETED -> R.drawable.ic_close + Type.CANCELLED, Type.FAILED -> R.drawable.ic_dots_vertical + else -> return } + holder.binding.uploadListAction.setImageResource(iconRes) + } - headerViewHolder.binding.autoUploadBatterySaverWarningCard.root + private fun bindHeaderBatterySaverWarning(holder: HeaderViewHolder) { + holder.binding.autoUploadBatterySaverWarningCard.root .setVisibleIf(powerManagementService.isPowerSavingEnabled) - viewThemeUtils.material.themeCardView(headerViewHolder.binding.autoUploadBatterySaverWarningCard.root) + viewThemeUtils.material.themeCardView(holder.binding.autoUploadBatterySaverWarningCard.root) + } - headerViewHolder.binding.uploadListAction.setOnClickListener { + private fun bindHeaderActionClickListener(holder: HeaderViewHolder, group: Section) { + holder.binding.uploadListAction.setOnClickListener { when (group.type) { - Type.CURRENT -> { - val items = group.items - if (items.isEmpty()) return@setOnClickListener - - val accountName = items[0].accountName - - val totalUploads = group.items.size - val completedCount = intArrayOf(0) - - for (i in group.items.indices) { - val upload = group.items[i] - uploadHelper.updateUploadStatus( - upload.remotePath, - accountName, - UploadStatus.UPLOAD_CANCELLED - ) { - cancelUpload(upload.remotePath, accountName) { - completedCount[0]++ - if (completedCount[0] == totalUploads) { - Log_OC.d(TAG, "refreshing upload items") - - // All uploads finished, refresh UI once - loadUploadItemsFromDb(Runnable {}) - } - } - } - } - } - - Type.COMPLETED -> { - uploadsStorageManager.clearSuccessfulUploads() - loadUploadItemsFromDb {} - } - - Type.FAILED -> showFailedPopupMenu(headerViewHolder) - Type.CANCELLED -> showCancelledPopupMenu(headerViewHolder) + Type.CURRENT -> cancelAllCurrentUploads(group) + Type.COMPLETED -> { uploadsStorageManager.clearSuccessfulUploads(); loadUploadItemsFromDb {} } + Type.FAILED -> showFailedPopupMenu(holder) + Type.CANCELLED -> showCancelledPopupMenu(holder) else -> {} } } } - private fun showFailedPopupMenu(headerViewHolder: HeaderViewHolder) { - val failedPopup = PopupMenu(MainApp.getAppContext(), headerViewHolder.binding.uploadListAction) - failedPopup.inflate(R.menu.upload_list_failed_options) - failedPopup.setOnMenuItemClickListener { i: MenuItem? -> - val itemId = i!!.itemId - if (itemId == R.id.action_upload_list_failed_clear) { - uploadsStorageManager.clearFailedButNotDelayedUploads() - clearTempEncryptedFolder() - loadUploadItemsFromDb {} - } else if (itemId == R.id.action_upload_list_failed_retry) { - uploadHelper.retryFailedUploads( - uploadsStorageManager, - connectivityService, - accountManager, - powerManagementService - ) + private fun cancelAllCurrentUploads(group: Section) { + val items = group.items.takeIf { it.isNotEmpty() } ?: return + val accountName = items[0].accountName + var completedCount = 0 + items.forEach { upload -> + uploadHelper.updateUploadStatus(upload.remotePath, accountName, UploadStatus.UPLOAD_CANCELLED) { + cancelUpload(upload.remotePath, accountName) { + completedCount++ + if (completedCount == items.size) { + Log_OC.d(TAG, "refreshing upload items") + loadUploadItemsFromDb {} + } + } } - true } - - failedPopup.show() } - private fun showCancelledPopupMenu(headerViewHolder: HeaderViewHolder) { - val popup = PopupMenu(MainApp.getAppContext(), headerViewHolder.binding.uploadListAction) - popup.inflate(R.menu.upload_list_cancelled_options) - - popup.setOnMenuItemClickListener { i: MenuItem -> - val itemId = i.itemId - if (itemId == R.id.action_upload_list_cancelled_clear) { - uploadsStorageManager.clearCancelledUploadsForCurrentAccount() - loadUploadItemsFromDb(Runnable {}) - clearTempEncryptedFolder() - } else if (itemId == R.id.action_upload_list_cancelled_resume) { - retryCancelledUploads() + private fun showFailedPopupMenu(holder: HeaderViewHolder) { + PopupMenu(fileActivity, holder.binding.uploadListAction).apply { + inflate(R.menu.upload_list_failed_options) + setOnMenuItemClickListener { item -> + when (item.itemId) { + R.id.action_upload_list_failed_clear -> { + uploadsStorageManager.clearFailedButNotDelayedUploads() + clearTempEncryptedFolder() + loadUploadItemsFromDb {} + } + R.id.action_upload_list_failed_retry -> + uploadHelper.retryFailedUploads( + uploadsStorageManager, connectivityService, accountManager, powerManagementService + ) + } + true } - true + show() } + } - popup.show() + private fun showCancelledPopupMenu(holder: HeaderViewHolder) { + PopupMenu(fileActivity, holder.binding.uploadListAction).apply { + inflate(R.menu.upload_list_cancelled_options) + setOnMenuItemClickListener { item -> + when (item.itemId) { + R.id.action_upload_list_cancelled_clear -> { + uploadsStorageManager.clearCancelledUploadsForCurrentAccount() + loadUploadItemsFromDb {} + clearTempEncryptedFolder() + } + R.id.action_upload_list_cancelled_resume -> retryCancelledUploads() + } + true + } + show() + } } private fun clearTempEncryptedFolder() { @@ -286,24 +272,18 @@ class UploadListAdapter( // FIXME For e2e resume is not working private fun retryCancelledUploads() { - Thread { + fileActivity.lifecycleScope.launch(Dispatchers.IO) { val showNotExistMessage = uploadHelper.retryCancelledUploads( - uploadsStorageManager, - connectivityService, - accountManager, - powerManagementService + uploadsStorageManager, connectivityService, accountManager, powerManagementService ) - parentActivity.runOnUiThread { - if (showNotExistMessage) { - showNotExistMessage() + if (showNotExistMessage) { + withContext(Dispatchers.Main) { + DisplayUtils.showSnackMessage(parentActivity, R.string.upload_action_file_not_exist_message) } } - }.start() - } - - private fun showNotExistMessage() { - DisplayUtils.showSnackMessage(parentActivity, R.string.upload_action_file_not_exist_message) + } } + // endregion override fun onBindFooterViewHolder(holder: SectionedViewHolder?, section: Int) = Unit @@ -893,7 +873,12 @@ class UploadListAdapter( sections.indices.forEach { i -> val sec = sections[i] - uploadHelper.getUploadsByStatus(accountName, sec.status!!, capabilities, sec.collisionPolicy) { uploads -> + uploadHelper.getUploadsByStatus( + accountName, + sec.status!!, + capabilities, + sec.collisionPolicy + ) { uploads -> uploads.forEach { it.setDataFixed(uploadHelper) } sections[i] = sec.withItems(uploads.sortedByUploadOrder()) parentActivity.runOnUiThread { @@ -962,18 +947,6 @@ class UploadListAdapter( } } - internal class HeaderViewHolder(var binding: UploadListHeaderBinding) : SectionedViewHolder( - binding.getRoot() - ) - - internal class ItemViewHolder(var binding: UploadListItemBinding) : SectionedViewHolder( - binding.getRoot() - ) - - internal enum class Type { - CURRENT, COMPLETED, FAILED, CANCELLED, SKIPPED - } - fun cancelOldErrorNotification(upload: OCUpload?) { if (mNotificationManager == null) { mNotificationManager = parentActivity.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager? From a33064e0e6609e35050a45c2acd70005e4b11eef Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 27 Mar 2026 15:44:01 +0100 Subject: [PATCH 04/11] refactor(upload-list-adapter): fix codacy Signed-off-by: alperozturk96 --- .../utils/extensions/OCUploadExtensions.kt | 13 +- .../android/ui/adapter/UploadListAdapter.kt | 787 +++++++++--------- ...mparatorTest.kt => OCUploadSortingTest.kt} | 0 3 files changed, 407 insertions(+), 393 deletions(-) rename app/src/test/java/com/owncloud/android/ui/db/{OCUploadComparatorTest.kt => OCUploadSortingTest.kt} (100%) diff --git a/app/src/main/java/com/nextcloud/utils/extensions/OCUploadExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/OCUploadExtensions.kt index 583184c78f9a..933536db1ff2 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/OCUploadExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/OCUploadExtensions.kt @@ -13,10 +13,9 @@ fun List.getUploadIds(): LongArray = map { it.uploadId }.toLongArray() fun Array.getUploadIds(): LongArray = map { it.uploadId }.toLongArray() -fun List.sortedByUploadOrder(): List = - sortedWith( - compareBy { it.fixedUploadStatus } - .thenByDescending { it.isFixedUploadingNow } - .thenByDescending { it.fixedUploadEndTimeStamp } - .thenBy { it.fixedUploadId } - ) +fun List.sortedByUploadOrder(): List = sortedWith( + compareBy { it.fixedUploadStatus } + .thenByDescending { it.isFixedUploadingNow } + .thenByDescending { it.fixedUploadEndTimeStamp } + .thenBy { it.fixedUploadId } +) diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.kt index f0d70998a5cf..a6446574c922 100755 --- a/app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.kt @@ -14,7 +14,6 @@ import android.content.Intent import android.net.Uri import android.text.format.DateUtils import android.view.LayoutInflater -import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.widget.PopupMenu @@ -32,7 +31,6 @@ import com.nextcloud.client.jobs.upload.FileUploadWorker.Companion.cancelUpload import com.nextcloud.client.network.ConnectivityService import com.nextcloud.utils.extensions.setVisibleIf import com.nextcloud.utils.extensions.sortedByUploadOrder -import com.owncloud.android.MainApp import com.owncloud.android.R import com.owncloud.android.databinding.UploadListHeaderBinding import com.owncloud.android.databinding.UploadListItemBinding @@ -66,8 +64,8 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.File import java.util.function.Consumer -import java.util.function.Supplier +@Suppress("TooManyFunctions", "LargeClass", "LongParameterList", "NestedBlockDepth", "MaxLineLength", "ReturnCount") class UploadListAdapter( private val fileActivity: FileActivity, private val uploadsStorageManager: UploadsStorageManager, @@ -91,11 +89,9 @@ class UploadListAdapter( internal enum class Type { CURRENT, COMPLETED, FAILED, CANCELLED, SKIPPED } - internal class HeaderViewHolder(val binding: UploadListHeaderBinding) : - SectionedViewHolder(binding.root) + internal class HeaderViewHolder(val binding: UploadListHeaderBinding) : SectionedViewHolder(binding.root) - internal class ItemViewHolder(val binding: UploadListItemBinding) : - SectionedViewHolder(binding.root) + internal class ItemViewHolder(val binding: UploadListItemBinding) : SectionedViewHolder(binding.root) private val sections: MutableList
= ArrayList
( listOf( @@ -104,7 +100,7 @@ class UploadListAdapter( R.string.uploads_view_group_current_uploads, UploadStatus.UPLOAD_IN_PROGRESS, null, - listOf(), + listOf() ), Section( Type.FAILED, @@ -148,13 +144,9 @@ class UploadListAdapter( shouldShowHeadersForEmptySections(false) } - override fun getSectionCount(): Int { - return sections.size - } + override fun getSectionCount(): Int = sections.size - override fun getItemCount(section: Int): Int { - return sections[section].items.size - } + override fun getItemCount(section: Int): Int = sections[section].items.size // region header override fun onBindHeaderViewHolder(holder: SectionedViewHolder?, section: Int, expanded: Boolean) { @@ -185,7 +177,7 @@ class UploadListAdapter( private fun bindHeaderActionButton(holder: HeaderViewHolder, group: Section) { val iconRes = when (group.type) { Type.CURRENT, Type.COMPLETED -> R.drawable.ic_close - Type.CANCELLED, Type.FAILED -> R.drawable.ic_dots_vertical + Type.CANCELLED, Type.FAILED -> R.drawable.ic_dots_vertical else -> return } holder.binding.uploadListAction.setImageResource(iconRes) @@ -200,10 +192,17 @@ class UploadListAdapter( private fun bindHeaderActionClickListener(holder: HeaderViewHolder, group: Section) { holder.binding.uploadListAction.setOnClickListener { when (group.type) { - Type.CURRENT -> cancelAllCurrentUploads(group) - Type.COMPLETED -> { uploadsStorageManager.clearSuccessfulUploads(); loadUploadItemsFromDb {} } - Type.FAILED -> showFailedPopupMenu(holder) + Type.CURRENT -> cancelAllCurrentUploads(group) + + Type.COMPLETED -> { + uploadsStorageManager.clearSuccessfulUploads() + loadUploadItemsFromDb {} + } + + Type.FAILED -> showFailedPopupMenu(holder) + Type.CANCELLED -> showCancelledPopupMenu(holder) + else -> {} } } @@ -236,9 +235,13 @@ class UploadListAdapter( clearTempEncryptedFolder() loadUploadItemsFromDb {} } + R.id.action_upload_list_failed_retry -> uploadHelper.retryFailedUploads( - uploadsStorageManager, connectivityService, accountManager, powerManagementService + uploadsStorageManager, + connectivityService, + accountManager, + powerManagementService ) } true @@ -257,6 +260,7 @@ class UploadListAdapter( loadUploadItemsFromDb {} clearTempEncryptedFolder() } + R.id.action_upload_list_cancelled_resume -> retryCancelledUploads() } true @@ -267,14 +271,21 @@ class UploadListAdapter( private fun clearTempEncryptedFolder() { val user = parentActivity.user - user.ifPresent(Consumer { value: User? -> FileDataStorageManager.clearTempEncryptedFolder(value!!.accountName) }) + user.ifPresent( + Consumer { value: User? -> + FileDataStorageManager.clearTempEncryptedFolder(value!!.accountName) + } + ) } // FIXME For e2e resume is not working private fun retryCancelledUploads() { fileActivity.lifecycleScope.launch(Dispatchers.IO) { val showNotExistMessage = uploadHelper.retryCancelledUploads( - uploadsStorageManager, connectivityService, accountManager, powerManagementService + uploadsStorageManager, + connectivityService, + accountManager, + powerManagementService ) if (showNotExistMessage) { withContext(Dispatchers.Main) { @@ -285,38 +296,31 @@ class UploadListAdapter( } // endregion - override fun onBindFooterViewHolder(holder: SectionedViewHolder?, section: Int) = Unit - + // region content override fun onBindViewHolder( holder: SectionedViewHolder?, section: Int, relativePosition: Int, absolutePosition: Int ) { - if (sections.isEmpty() || section < 0 || section >= sections.size) { - return - } - - val sectionData = sections[section] - val item = sectionData.items[relativePosition] - + if (sections.isEmpty() || section !in sections.indices) return + val item = sections[section].items[relativePosition] val itemViewHolder = holder as ItemViewHolder - itemViewHolder.binding.uploadName.text = item.localPath - // local file name - val remoteFile = File(item.remotePath) - var fileName = remoteFile.getName() - if (fileName.isEmpty()) { - fileName = File.separator - } - itemViewHolder.binding.uploadName.text = fileName + bindItemText(holder, item) + bindItemStatus(itemViewHolder, item) + bindItemActions(itemViewHolder, item) + bindItemThumbnail(itemViewHolder, item) + } - // remote path to parent folder - itemViewHolder.binding.uploadRemotePath.text = File(item.remotePath).getParent() + @SuppressLint("SetTextI18n") + private fun bindItemText(holder: ItemViewHolder, item: OCUpload) { + val remoteFile = File(item.remotePath) + val fileName = remoteFile.name.takeIf { it.isNotEmpty() } ?: File.separator + holder.binding.uploadName.text = fileName + holder.binding.uploadRemotePath.text = File(item.remotePath).parent val updateTime = item.uploadEndTimestamp - - // file size if (item.fileSize != 0L) { var fileSizeFormat = "%s " @@ -327,305 +331,303 @@ class UploadListAdapter( val fileSizeInBytes = DisplayUtils.bytesToHumanReadable(item.fileSize) val uploadFileSize = String.format(fileSizeFormat, fileSizeInBytes) - itemViewHolder.binding.uploadFileSize.text = uploadFileSize + holder.binding.uploadFileSize.text = uploadFileSize } else { - itemViewHolder.binding.uploadFileSize.text = "" + holder.binding.uploadFileSize.text = "" } - // upload date - val showUploadDate = - (updateTime > 0 && item.uploadStatus == UploadStatus.UPLOAD_SUCCEEDED && item.lastResult == UploadResult.UPLOADED) - itemViewHolder.binding.uploadDate.visibility = if (showUploadDate) View.VISIBLE else View.GONE - if (showUploadDate) { - val dateString = DisplayUtils.getRelativeDateTimeString( + bindItemDate(holder, item, updateTime) + bindItemAccount(holder, item) + } + + private fun bindItemDate(holder: ItemViewHolder, item: OCUpload, updateTime: Long) { + val showDate = ( + updateTime > 0 && + item.uploadStatus == UploadStatus.UPLOAD_SUCCEEDED && + item.lastResult == UploadResult.UPLOADED + ) + + holder.binding.uploadDate.setVisibleIf(showDate) + + if (showDate) { + holder.binding.uploadDate.text = DisplayUtils.getRelativeDateTimeString( parentActivity, updateTime, DateUtils.MINUTE_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, 0 ) - itemViewHolder.binding.uploadDate.text = dateString } + } - // account - val optionalUser = accountManager.getUser(item.accountName) + private fun bindItemAccount(holder: ItemViewHolder, item: OCUpload) { if (showUser) { - itemViewHolder.binding.uploadAccount.visibility = View.VISIBLE - if (optionalUser.isPresent) { - itemViewHolder.binding.uploadAccount.text = DisplayUtils.getAccountNameDisplayText(optionalUser.get()) + holder.binding.uploadAccount.visibility = View.VISIBLE + val optionalUser = accountManager.getUser(item.accountName) + holder.binding.uploadAccount.text = if (optionalUser.isPresent) { + DisplayUtils.getAccountNameDisplayText(optionalUser.get()) } else { - itemViewHolder.binding.uploadAccount.text = item.accountName + item.accountName } } else { - itemViewHolder.binding.uploadAccount.visibility = View.GONE + holder.binding.uploadAccount.visibility = View.GONE } + } - // Reset fields visibility - itemViewHolder.binding.uploadRemotePath.visibility = View.VISIBLE - itemViewHolder.binding.uploadFileSize.visibility = View.VISIBLE - itemViewHolder.binding.uploadStatus.visibility = View.VISIBLE - itemViewHolder.binding.uploadProgressBar.visibility = View.GONE + private fun bindItemStatus(holder: ItemViewHolder, item: OCUpload) { + holder.binding.run { + uploadRemotePath.visibility = View.VISIBLE + uploadFileSize.visibility = View.VISIBLE + uploadStatus.visibility = View.VISIBLE + uploadProgressBar.visibility = View.GONE - // Update information depending of upload details - val status = getStatusText(item) - when (item.uploadStatus) { - UploadStatus.UPLOAD_IN_PROGRESS -> { - viewThemeUtils.platform.themeHorizontalProgressBar(itemViewHolder.binding.uploadProgressBar) - itemViewHolder.binding.uploadProgressBar.progress = 0 - itemViewHolder.binding.uploadProgressBar.visibility = View.VISIBLE - - if (uploadHelper.isUploadingNow(item)) { - // really uploading, so... - // ... unbind the old progress bar, if any; ... - if (uploadProgressListener != null) { - val upload = uploadProgressListener!!.upload - if (upload != null) { - val targetKey = buildRemoteName( - upload.accountName, - upload.remotePath - ) - uploadHelper.removeUploadTransferProgressListener(uploadProgressListener!!, targetKey) - } - } - // ... then, bind the current progress bar to listen for updates - uploadProgressListener = UploadProgressListener(item, itemViewHolder.binding.uploadProgressBar) - val targetKey = buildRemoteName(item.accountName, item.remotePath) - uploadHelper.addUploadTransferProgressListener(uploadProgressListener!!, targetKey) - } else { - // not really uploading; stop listening progress if view is reused! - if (uploadProgressListener != null && - uploadProgressListener!!.isWrapping(itemViewHolder.binding.uploadProgressBar) - ) { - val upload = uploadProgressListener!!.upload - if (upload != null) { - val targetKey = buildRemoteName( - upload.accountName, - upload.remotePath - ) - uploadHelper.removeUploadTransferProgressListener(uploadProgressListener!!, targetKey) - uploadProgressListener = null - } - } - } + val status = getStatusText(item) + when (item.uploadStatus) { + UploadStatus.UPLOAD_IN_PROGRESS -> bindItemInProgress(holder, item) - itemViewHolder.binding.uploadFileSize.visibility = View.GONE - itemViewHolder.binding.uploadProgressBar.invalidate() - } + UploadStatus.UPLOAD_SUCCEEDED, + UploadStatus.UPLOAD_CANCELLED -> uploadStatus.visibility = View.GONE - UploadStatus.UPLOAD_FAILED -> { + else -> {} } - UploadStatus.UPLOAD_SUCCEEDED, UploadStatus.UPLOAD_CANCELLED -> itemViewHolder.binding.uploadStatus.visibility = - View.GONE - } + // Override visibility for edge cases + if ((item.uploadStatus == UploadStatus.UPLOAD_SUCCEEDED && item.lastResult != UploadResult.UPLOADED) || + item.uploadStatus == UploadStatus.UPLOAD_CANCELLED + ) { + uploadStatus.visibility = View.VISIBLE + uploadFileSize.visibility = View.GONE + } - // show status if same file conflict or local file deleted or upload cancelled - if ((item.uploadStatus == UploadStatus.UPLOAD_SUCCEEDED && item.lastResult != UploadResult.UPLOADED) - || item.uploadStatus == UploadStatus.UPLOAD_CANCELLED - ) { - itemViewHolder.binding.uploadStatus.visibility = View.VISIBLE - itemViewHolder.binding.uploadFileSize.visibility = View.GONE + uploadStatus.text = status } + } - itemViewHolder.binding.uploadStatus.text = status + private fun bindItemInProgress(holder: ItemViewHolder, item: OCUpload) { + holder.binding.run { + viewThemeUtils.platform.themeHorizontalProgressBar(uploadProgressBar) + uploadProgressBar.progress = 0 + uploadProgressBar.visibility = View.VISIBLE + uploadFileSize.visibility = View.GONE - // bind listeners to perform actions - if (item.uploadStatus == UploadStatus.UPLOAD_IN_PROGRESS) { - // Cancel - itemViewHolder.binding.uploadRightButton.setImageResource(R.drawable.ic_action_cancel_grey) - itemViewHolder.binding.uploadRightButton.setVisibility(View.VISIBLE) - itemViewHolder.binding.uploadRightButton.setOnClickListener { - uploadHelper.updateUploadStatus( - item.remotePath, - item.accountName, - UploadStatus.UPLOAD_CANCELLED - ) { - cancelUpload(item.remotePath, item.accountName) { - loadUploadItemsFromDb {} - } + if (uploadHelper.isUploadingNow(item)) { + uploadProgressListener?.upload?.let { prevUpload -> + val key = buildRemoteName(prevUpload.accountName, prevUpload.remotePath) + uploadHelper.removeUploadTransferProgressListener(uploadProgressListener!!, key) } - } - } else if (item.uploadStatus == UploadStatus.UPLOAD_FAILED) { - if (item.lastResult == UploadResult.SYNC_CONFLICT) { - itemViewHolder.binding.uploadRightButton.setImageResource(R.drawable.ic_dots_vertical) - itemViewHolder.binding.uploadRightButton.setOnClickListener { view: View? -> - optionalUser.ifPresent( - Consumer { user: User? -> showItemConflictPopup(user, itemViewHolder, item, status, view) }) - } - } else { - // Delete - itemViewHolder.binding.uploadRightButton.setImageResource(R.drawable.ic_action_delete_grey) - itemViewHolder.binding.uploadRightButton.setOnClickListener { - removeUpload( - item - ) + uploadProgressListener = UploadProgressListener(item, uploadProgressBar) + uploadHelper.addUploadTransferProgressListener( + uploadProgressListener!!, + buildRemoteName(item.accountName, item.remotePath) + ) + } else if (uploadProgressListener?.isWrapping(uploadProgressBar) == true) { + uploadProgressListener?.upload?.let { prevUpload -> + val key = buildRemoteName(prevUpload.accountName, prevUpload.remotePath) + uploadHelper.removeUploadTransferProgressListener(uploadProgressListener!!, key) + uploadProgressListener = null } } - itemViewHolder.binding.uploadRightButton.setVisibility(View.VISIBLE) - } else { // UploadStatus.UPLOAD_SUCCEEDED - itemViewHolder.binding.uploadRightButton.setVisibility(View.INVISIBLE) - } - - itemViewHolder.binding.uploadListItemLayout.setOnClickListener(null) - - // Set icon or thumbnail - itemViewHolder.binding.thumbnail.setImageResource(R.drawable.file) - - // click on item - if (item.uploadStatus == UploadStatus.UPLOAD_FAILED || - item.uploadStatus == UploadStatus.UPLOAD_CANCELLED - ) { - val uploadResult = item.lastResult - itemViewHolder.binding.uploadListItemLayout.setOnClickListener { v: View? -> - if (uploadResult == UploadResult.CREDENTIAL_ERROR) { - val optUser = accountManager.getUser(item.accountName) - val user = optUser.orElseThrow(Supplier { RuntimeException() }) - parentActivity.fileOperationsHelper.checkCurrentCredentials(user) - return@setOnClickListener - } else if (uploadResult == UploadResult.SYNC_CONFLICT && optionalUser.isPresent) { - val user = optionalUser.get() - if (checkAndOpenConflictResolutionDialog(user, itemViewHolder, item, status)) { - return@setOnClickListener + + uploadProgressBar.invalidate() + } + } + + private fun bindItemActions(holder: ItemViewHolder, item: OCUpload) { + holder.binding.run { + val optionalUser = accountManager.getUser(item.accountName) + val status = getStatusText(item) + + // Right-side button + when (item.uploadStatus) { + UploadStatus.UPLOAD_IN_PROGRESS -> { + uploadRightButton.run { + setImageResource(R.drawable.ic_action_cancel_grey) + visibility = View.VISIBLE + setOnClickListener { + uploadHelper.updateUploadStatus( + item.remotePath, + item.accountName, + UploadStatus.UPLOAD_CANCELLED + ) { + cancelUpload(item.remotePath, item.accountName) { loadUploadItemsFromDb {} } + } + } } } - // not a credentials error - val file = File(item.localPath) - val user = accountManager.getUser(item.accountName) - if (file.exists() && user.isPresent) { - uploadHelper.retryUpload(item, user.get()) - } else { - DisplayUtils.showSnackMessage( - v!!.getRootView().findViewById(android.R.id.content), - R.string.local_file_not_found_message - ) + + UploadStatus.UPLOAD_FAILED -> { + uploadRightButton.run { + if (item.lastResult == UploadResult.SYNC_CONFLICT) { + setImageResource(R.drawable.ic_dots_vertical) + setOnClickListener { view -> + optionalUser.ifPresent { user -> + showItemConflictPopup(user, holder, item, status, view) + } + } + } else { + setImageResource(R.drawable.ic_action_delete_grey) + setOnClickListener { removeUpload(item) } + } + visibility = View.VISIBLE + } } + + else -> uploadRightButton.visibility = View.INVISIBLE } - } else if (item.uploadStatus == UploadStatus.UPLOAD_SUCCEEDED) { - itemViewHolder.binding.uploadListItemLayout.setOnClickListener { - onUploadedItemClick( - item - ) + + // Row click + uploadListItemLayout.run { + setOnClickListener(null) + when (item.uploadStatus) { + UploadStatus.UPLOAD_FAILED, UploadStatus.UPLOAD_CANCELLED -> + setOnClickListener { + onFailedOrCancelledItemClick(item, optionalUser, holder, status) + } + + UploadStatus.UPLOAD_SUCCEEDED -> + setOnClickListener { onUploadedItemClick(item) } + + else -> {} + } + } + + // Thumbnail click to open locally + if (item.uploadStatus != UploadStatus.UPLOAD_SUCCEEDED) { + thumbnail.setOnClickListener { onUploadingItemClick(item) } } } + } - // click on thumbnail to open locally - if (item.uploadStatus != UploadStatus.UPLOAD_SUCCEEDED) { - itemViewHolder.binding.thumbnail.setOnClickListener { - onUploadingItemClick( - item - ) + private fun onFailedOrCancelledItemClick( + item: OCUpload, + optionalUser: java.util.Optional, + holder: ItemViewHolder, + status: String + ) { + when (item.lastResult) { + UploadResult.CREDENTIAL_ERROR -> { + val user = optionalUser.orElseThrow { RuntimeException() } + parentActivity.fileOperationsHelper.checkCurrentCredentials(user) } + + UploadResult.SYNC_CONFLICT if optionalUser.isPresent -> { + if (checkAndOpenConflictResolutionDialog(optionalUser.get(), holder, item, status)) return + retryOrShowError(item) + } + + else -> retryOrShowError(item) } + } - /* - * Cancellation needs do be checked and done before changing the drawable in fileIcon, or - * {@link ThumbnailsCacheManager#cancelPotentialWork} will NEVER cancel any task. - */ - val fakeFileToCheatThumbnailsCacheManagerInterface = OCFile(item.remotePath) - fakeFileToCheatThumbnailsCacheManagerInterface.setStoragePath(item.localPath) - fakeFileToCheatThumbnailsCacheManagerInterface.mimeType = item.mimeType + private fun retryOrShowError(item: OCUpload) { + val file = File(item.localPath) + val user = accountManager.getUser(item.accountName) + if (file.exists() && user.isPresent) { + uploadHelper.retryUpload(item, user.get()) + } else { + DisplayUtils.showSnackMessage( + fileActivity, + R.string.local_file_not_found_message + ) + } + } - val allowedToCreateNewThumbnail = ThumbnailsCacheManager.cancelPotentialThumbnailWork( - fakeFileToCheatThumbnailsCacheManagerInterface, itemViewHolder.binding.thumbnail - ) + private fun bindItemThumbnail(holder: ItemViewHolder, item: OCUpload) { + holder.binding.thumbnail.setImageResource(R.drawable.file) - // TODO this code is duplicated; refactor to a common place - if (MimeTypeUtil.isImage(fakeFileToCheatThumbnailsCacheManagerInterface) - && fakeFileToCheatThumbnailsCacheManagerInterface.remoteId != null && item.uploadStatus == UploadStatus.UPLOAD_SUCCEEDED - ) { - // Thumbnail in Cache? + val fakeFile = OCFile(item.remotePath).apply { + setStoragePath(item.localPath) + mimeType = item.mimeType + } - val cacheKey = fakeFileToCheatThumbnailsCacheManagerInterface.remoteId.toString() - var thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(cacheKey) + val allowedToCreateNewThumbnail = + ThumbnailsCacheManager.cancelPotentialThumbnailWork(fakeFile, holder.binding.thumbnail) - if (thumbnail != null && !fakeFileToCheatThumbnailsCacheManagerInterface.isUpdateThumbnailNeeded) { - itemViewHolder.binding.thumbnail.setImageBitmap(thumbnail) - } else { - // generate new Thumbnail - val user = parentActivity.user - if (allowedToCreateNewThumbnail && user.isPresent) { - val task = - ThumbnailGenerationTask( - itemViewHolder.binding.thumbnail, - parentActivity.storageManager, - user.get() - ) - if (thumbnail == null) { - if (MimeTypeUtil.isVideo(fakeFileToCheatThumbnailsCacheManagerInterface)) { - thumbnail = ThumbnailsCacheManager.mDefaultVideo - } else { - thumbnail = ThumbnailsCacheManager.mDefaultImg - } - } - val asyncDrawable = - AsyncThumbnailDrawable( - parentActivity.getResources(), - thumbnail, - task - ) - itemViewHolder.binding.thumbnail.setImageDrawable(asyncDrawable) - task.execute( - ThumbnailGenerationTaskObject( - fakeFileToCheatThumbnailsCacheManagerInterface, null - ) - ) - } - } + val optionalUser = accountManager.getUser(item.accountName) + val fileName = File(item.remotePath).name.takeIf { it.isNotEmpty() } ?: File.separator - if ("image/png" == item.mimeType) { - val backgroundColor = ContextCompat.getColor(parentActivity, R.color.bg_default) - itemViewHolder.binding.thumbnail.setBackgroundColor(backgroundColor) - } - } else if (MimeTypeUtil.isImage(fakeFileToCheatThumbnailsCacheManagerInterface)) { - val file = File(item.localPath) - var thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(file.hashCode().toString()) + when { + MimeTypeUtil.isImage(fakeFile) && fakeFile.remoteId != null && + item.uploadStatus == UploadStatus.UPLOAD_SUCCEEDED -> + bindRemoteThumbnail(holder, item, fakeFile, allowedToCreateNewThumbnail) - if (thumbnail != null) { - itemViewHolder.binding.thumbnail.setImageBitmap(thumbnail) - } else if (allowedToCreateNewThumbnail) { - getThumbnailFromFileTypeAndSetIcon(item.localPath, itemViewHolder) + MimeTypeUtil.isImage(fakeFile) -> + bindLocalThumbnail(holder, item, allowedToCreateNewThumbnail) - val task = - ThumbnailGenerationTask(itemViewHolder.binding.thumbnail) + optionalUser.isPresent -> { + val icon = MimeTypeUtil.getFileTypeIcon(item.mimeType, fileName, parentActivity, viewThemeUtils) + holder.binding.thumbnail.setImageDrawable(icon) + } + } + } - if (MimeTypeUtil.isVideo(file)) { - thumbnail = ThumbnailsCacheManager.mDefaultVideo + private fun bindRemoteThumbnail( + holder: ItemViewHolder, + item: OCUpload, + fakeFile: OCFile, + allowedToCreateNewThumbnail: Boolean + ) { + val cacheKey = fakeFile.remoteId.toString() + var thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(cacheKey) + + if (thumbnail != null && !fakeFile.isUpdateThumbnailNeeded) { + holder.binding.thumbnail.setImageBitmap(thumbnail) + } else if (allowedToCreateNewThumbnail) { + val user = parentActivity.user + if (user.isPresent) { + val task = ThumbnailGenerationTask(holder.binding.thumbnail, parentActivity.storageManager, user.get()) + thumbnail = thumbnail ?: if (MimeTypeUtil.isVideo(fakeFile)) { + ThumbnailsCacheManager.mDefaultVideo } else { - thumbnail = ThumbnailsCacheManager.mDefaultImg + ThumbnailsCacheManager.mDefaultImg } + holder.binding.thumbnail.setImageDrawable( + AsyncThumbnailDrawable(parentActivity.resources, thumbnail, task) + ) + task.execute(ThumbnailGenerationTaskObject(fakeFile, null)) + } + } - val asyncDrawable = - AsyncThumbnailDrawable(parentActivity.getResources(), thumbnail, task) - task.execute(ThumbnailGenerationTaskObject(file, null)) - task.setListener(object : ThumbnailGenerationTask.Listener { - override fun onSuccess() { - itemViewHolder.binding.thumbnail.setImageDrawable(asyncDrawable) - } + if (item.mimeType == "image/png") { + holder.binding.thumbnail.setBackgroundColor(ContextCompat.getColor(parentActivity, R.color.bg_default)) + } + } - override fun onError() { - getThumbnailFromFileTypeAndSetIcon(item.localPath, itemViewHolder) - } - }) + private fun bindLocalThumbnail(holder: ItemViewHolder, item: OCUpload, allowedToCreateNewThumbnail: Boolean) { + val file = File(item.localPath) + val thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(file.hashCode().toString()) - Log_OC.v(TAG, "Executing task to generate a new thumbnail") + if (thumbnail != null) { + holder.binding.thumbnail.setImageBitmap(thumbnail) + } else if (allowedToCreateNewThumbnail) { + getThumbnailFromFileTypeAndSetIcon(item.localPath, holder) + val task = ThumbnailGenerationTask(holder.binding.thumbnail) + val defaultThumbnail = if (MimeTypeUtil.isVideo(file)) { + ThumbnailsCacheManager.mDefaultVideo + } else { + ThumbnailsCacheManager.mDefaultImg } + val asyncDrawable = AsyncThumbnailDrawable(parentActivity.resources, defaultThumbnail, task) + task.execute(ThumbnailGenerationTaskObject(file, null)) + task.setListener(object : ThumbnailGenerationTask.Listener { + override fun onSuccess() { + holder.binding.thumbnail.setImageDrawable(asyncDrawable) + } - if ("image/png".equals(item.mimeType, ignoreCase = true)) { - val backgroundColor = ContextCompat.getColor(parentActivity, R.color.bg_default) - itemViewHolder.binding.thumbnail.setBackgroundColor(backgroundColor) - } - } else { - if (optionalUser.isPresent) { - val icon = MimeTypeUtil.getFileTypeIcon( - item.mimeType, - fileName, - parentActivity, - viewThemeUtils - ) - itemViewHolder.binding.thumbnail.setImageDrawable(icon) - } + override fun onError() { + getThumbnailFromFileTypeAndSetIcon(item.localPath, holder) + } + }) + Log_OC.v(TAG, "Executing task to generate a new thumbnail") + } + + if (item.mimeType.equals("image/png", ignoreCase = true)) { + holder.binding.thumbnail.setBackgroundColor(ContextCompat.getColor(parentActivity, R.color.bg_default)) } } + // endregion + + override fun onBindFooterViewHolder(holder: SectionedViewHolder?, section: Int) = Unit private fun getThumbnailFromFileTypeAndSetIcon(localPath: String?, itemViewHolder: ItemViewHolder) { val drawable = MimeTypeUtil.getIcon(localPath, parentActivity, viewThemeUtils) ?: return @@ -663,25 +665,25 @@ class UploadListAdapter( } private fun refreshFolderAndUpdateUI( - holder: ItemViewHolder, user: User?, folder: OCFile?, remotePath: String?, - item: OCUpload, status: String? + holder: ItemViewHolder, + user: User?, + folder: OCFile?, + remotePath: String?, + item: OCUpload, + status: String? ) { - val context = MainApp.getAppContext() - - this.refreshFolder( - context, + refreshFolder( holder, user, folder ) { _: RemoteOperation<*>?, result: RemoteOperationResult<*>? -> holder.binding.uploadStatus.text = status - if (result!!.isSuccess) { + if (result?.isSuccess == true) { val fileOnServer = storageManager.getFileByEncryptedRemotePath(remotePath) - if (fileOnServer != null) { openConflictActivity(fileOnServer, item) } else { - displayFileNotFoundError(holder.itemView, context) + displayFileNotFoundError(holder.itemView, fileActivity) } } } @@ -694,23 +696,23 @@ class UploadListAdapter( private fun showItemConflictPopup( user: User?, - itemViewHolder: ItemViewHolder, + holder: ItemViewHolder, item: OCUpload, status: String?, view: View? ) { - val popup = PopupMenu(MainApp.getAppContext(), view) - popup.inflate(R.menu.upload_list_item_file_conflict) - popup.setOnMenuItemClickListener { i: MenuItem? -> - val itemId = i!!.itemId - if (itemId == R.id.action_upload_list_resolve_conflict) { - checkAndOpenConflictResolutionDialog(user, itemViewHolder, item, status) - } else { - removeUpload(item) + PopupMenu(fileActivity, view).apply { + inflate(R.menu.upload_list_item_file_conflict) + setOnMenuItemClickListener { menuItem -> + if (menuItem.itemId == R.id.action_upload_list_resolve_conflict) { + checkAndOpenConflictResolutionDialog(user, holder, item, status) + } else { + removeUpload(item) + } + true } - true + show() } - popup.show() } fun removeUpload(item: OCUpload?) { @@ -719,13 +721,7 @@ class UploadListAdapter( loadUploadItemsFromDb {} } - private fun refreshFolder( - context: Context?, - view: ItemViewHolder, - user: User?, - folder: OCFile?, - listener: OnRemoteOperationListener - ) { + private fun refreshFolder(view: ItemViewHolder, user: User?, folder: OCFile?, listener: OnRemoteOperationListener) { view.binding.uploadListItemLayout.isClickable = false view.binding.uploadStatus.setText(R.string.uploads_view_upload_status_fetching_server_version) RefreshFolderOperation( @@ -736,23 +732,16 @@ class UploadListAdapter( true, storageManager, user, - context + fileActivity ) - .execute( - user, - context, - { caller: RemoteOperation<*>?, result: RemoteOperationResult<*>? -> - view.binding.uploadListItemLayout.isClickable = true - listener.onRemoteOperationFinish(caller, result) - }, - parentActivity.handler - ) + .execute(user, fileActivity, { caller, result -> + view.binding.uploadListItemLayout.isClickable = true + listener.onRemoteOperationFinish(caller, result) + }, parentActivity.handler) } private fun openConflictActivity(file: OCFile, upload: OCUpload) { file.setStoragePath(upload.localPath) - - val context = MainApp.getAppContext() val user = accountManager.getUser(upload.accountName) if (user.isPresent) { val intent = createIntent( @@ -760,99 +749,127 @@ class UploadListAdapter( user.get(), upload.uploadId, Intent.FLAG_ACTIVITY_NEW_TASK, - context + fileActivity ) - - context.startActivity(intent) + fileActivity.startActivity(intent) } } /** * Gets the status text to show to the user according to the status and last result of the the given upload. - * + * * @param upload Upload to describe. * @return Text describing the status of the given upload. */ private fun getStatusText(upload: OCUpload): String { val status: String - val statusRes = parentActivity.getResources() + val res = parentActivity.getResources() val prefs = parentActivity.appPreferences - val uploadStatus = upload.uploadStatus - - when (uploadStatus) { + when (val uploadStatus = upload.uploadStatus) { UploadStatus.UPLOAD_IN_PROGRESS -> { status = if (prefs.isGlobalUploadPaused()) { - statusRes.getString(R.string.upload_global_pause_title) + res.getString(R.string.upload_global_pause_title) } else if (uploadHelper.isUploadingNow(upload)) { - statusRes.getString(R.string.uploader_upload_in_progress_ticker) + res.getString(R.string.uploader_upload_in_progress_ticker) } else { - statusRes.getString(R.string.uploads_view_later_waiting_to_upload) + res.getString(R.string.uploads_view_later_waiting_to_upload) } } UploadStatus.UPLOAD_SUCCEEDED -> { val result = upload.lastResult status = if (result == UploadResult.SAME_FILE_CONFLICT) { - statusRes.getString(R.string.uploads_view_upload_status_succeeded_same_file) + res.getString(R.string.uploads_view_upload_status_succeeded_same_file) } else if (result == UploadResult.FILE_NOT_FOUND) { getUploadFailedStatusText(result) } else if (upload.nameCollisionPolicy == NameCollisionPolicy.SKIP) { - statusRes.getString(R.string.uploads_view_upload_status_skip_reason) + res.getString(R.string.uploads_view_upload_status_skip_reason) } else { - statusRes.getString(R.string.uploads_view_upload_status_succeeded) + res.getString(R.string.uploads_view_upload_status_succeeded) } } UploadStatus.UPLOAD_FAILED -> status = getUploadFailedStatusText(upload.lastResult) - UploadStatus.UPLOAD_CANCELLED -> status = statusRes.getString(R.string.upload_manually_cancelled) + + UploadStatus.UPLOAD_CANCELLED -> status = res.getString(R.string.upload_manually_cancelled) + else -> status = "Uncontrolled status: $uploadStatus" } return status } - private fun getUploadFailedStatusText(result: UploadResult): String { - return when (result) { - UploadResult.CREDENTIAL_ERROR -> parentActivity.getString(R.string.uploads_view_upload_status_failed_credentials_error) - UploadResult.FOLDER_ERROR -> parentActivity.getString(R.string.uploads_view_upload_status_failed_folder_error) - UploadResult.FILE_NOT_FOUND -> parentActivity.getString(R.string.uploads_view_upload_status_failed_localfile_error) - UploadResult.FILE_ERROR -> parentActivity.getString(R.string.uploads_view_upload_status_failed_file_error) - UploadResult.PRIVILEGES_ERROR -> parentActivity.getString(R.string.uploads_view_upload_status_failed_permission_error) - UploadResult.NETWORK_CONNECTION -> parentActivity.getString(R.string.uploads_view_upload_status_failed_connection_error) - UploadResult.DELAYED_FOR_WIFI -> parentActivity.getString(R.string.uploads_view_upload_status_waiting_for_wifi) - UploadResult.DELAYED_FOR_CHARGING -> parentActivity.getString(R.string.uploads_view_upload_status_waiting_for_charging) - UploadResult.CONFLICT_ERROR -> parentActivity.getString(R.string.uploads_view_upload_status_conflict) - UploadResult.SERVICE_INTERRUPTED -> parentActivity.getString(R.string.uploads_view_upload_status_service_interrupted) - UploadResult.CANCELLED -> // should not get here ; cancelled uploads should be wiped out - parentActivity.getString(R.string.uploads_view_upload_status_cancelled) - - UploadResult.UPLOADED -> // should not get here ; status should be UPLOAD_SUCCESS - parentActivity.getString(R.string.uploads_view_upload_status_succeeded) - - UploadResult.MAINTENANCE_MODE -> parentActivity.getString(R.string.maintenance_mode) - UploadResult.SSL_RECOVERABLE_PEER_UNVERIFIED -> parentActivity.getString( - R.string.uploads_view_upload_status_failed_ssl_certificate_not_trusted - ) + private fun getUploadFailedStatusText(result: UploadResult): String = when (result) { + UploadResult.CREDENTIAL_ERROR -> + parentActivity.getString(R.string.uploads_view_upload_status_failed_credentials_error) - UploadResult.UNKNOWN -> parentActivity.getString(R.string.uploads_view_upload_status_unknown_fail) - UploadResult.LOCK_FAILED -> parentActivity.getString(R.string.upload_lock_failed) - UploadResult.DELAYED_IN_POWER_SAVE_MODE -> parentActivity.getString( - R.string.uploads_view_upload_status_waiting_exit_power_save_mode - ) + UploadResult.FOLDER_ERROR -> + parentActivity.getString(R.string.uploads_view_upload_status_failed_folder_error) - UploadResult.VIRUS_DETECTED -> parentActivity.getString(R.string.uploads_view_upload_status_virus_detected) - UploadResult.LOCAL_STORAGE_FULL -> parentActivity.getString(R.string.upload_local_storage_full) - UploadResult.OLD_ANDROID_API -> parentActivity.getString(R.string.upload_old_android) - UploadResult.SYNC_CONFLICT -> parentActivity.getString(R.string.upload_sync_conflict) - UploadResult.CANNOT_CREATE_FILE -> parentActivity.getString(R.string.upload_cannot_create_file) - UploadResult.LOCAL_STORAGE_NOT_COPIED -> parentActivity.getString(R.string.upload_local_storage_not_copied) - UploadResult.QUOTA_EXCEEDED -> parentActivity.getString(R.string.upload_quota_exceeded) - else -> parentActivity.getString(R.string.upload_unknown_error) - } + UploadResult.FILE_NOT_FOUND -> + parentActivity.getString(R.string.uploads_view_upload_status_failed_localfile_error) + + UploadResult.FILE_ERROR -> parentActivity.getString(R.string.uploads_view_upload_status_failed_file_error) + + UploadResult.PRIVILEGES_ERROR -> parentActivity.getString( + R.string.uploads_view_upload_status_failed_permission_error + ) + + UploadResult.NETWORK_CONNECTION -> + parentActivity.getString(R.string.uploads_view_upload_status_failed_connection_error) + + UploadResult.DELAYED_FOR_WIFI -> parentActivity.getString( + R.string.uploads_view_upload_status_waiting_for_wifi + ) + + UploadResult.DELAYED_FOR_CHARGING -> + parentActivity.getString(R.string.uploads_view_upload_status_waiting_for_charging) + + UploadResult.CONFLICT_ERROR -> parentActivity.getString(R.string.uploads_view_upload_status_conflict) + + UploadResult.SERVICE_INTERRUPTED -> parentActivity.getString( + R.string.uploads_view_upload_status_service_interrupted + ) + + UploadResult.CANCELLED -> // should not get here ; cancelled uploads should be wiped out + parentActivity.getString(R.string.uploads_view_upload_status_cancelled) + + UploadResult.UPLOADED -> // should not get here ; status should be UPLOAD_SUCCESS + parentActivity.getString(R.string.uploads_view_upload_status_succeeded) + + UploadResult.MAINTENANCE_MODE -> parentActivity.getString(R.string.maintenance_mode) + + UploadResult.SSL_RECOVERABLE_PEER_UNVERIFIED -> parentActivity.getString( + R.string.uploads_view_upload_status_failed_ssl_certificate_not_trusted + ) + + UploadResult.UNKNOWN -> parentActivity.getString(R.string.uploads_view_upload_status_unknown_fail) + + UploadResult.LOCK_FAILED -> parentActivity.getString(R.string.upload_lock_failed) + + UploadResult.DELAYED_IN_POWER_SAVE_MODE -> parentActivity.getString( + R.string.uploads_view_upload_status_waiting_exit_power_save_mode + ) + + UploadResult.VIRUS_DETECTED -> parentActivity.getString(R.string.uploads_view_upload_status_virus_detected) + + UploadResult.LOCAL_STORAGE_FULL -> parentActivity.getString(R.string.upload_local_storage_full) + + UploadResult.OLD_ANDROID_API -> parentActivity.getString(R.string.upload_old_android) + + UploadResult.SYNC_CONFLICT -> parentActivity.getString(R.string.upload_sync_conflict) + + UploadResult.CANNOT_CREATE_FILE -> parentActivity.getString(R.string.upload_cannot_create_file) + + UploadResult.LOCAL_STORAGE_NOT_COPIED -> parentActivity.getString(R.string.upload_local_storage_not_copied) + + UploadResult.QUOTA_EXCEEDED -> parentActivity.getString(R.string.upload_quota_exceeded) + + else -> parentActivity.getString(R.string.upload_unknown_error) } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SectionedViewHolder { - return if (viewType == VIEW_TYPE_HEADER) { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SectionedViewHolder = + if (viewType == VIEW_TYPE_HEADER) { HeaderViewHolder( UploadListHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false) ) @@ -861,7 +878,6 @@ class UploadListAdapter( UploadListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) ) } - } @SuppressLint("NotifyDataSetChanged") fun loadUploadItemsFromDb(onCompleted: Runnable) { @@ -916,7 +932,7 @@ class UploadListAdapter( val optionalUser = parentActivity.user if (canBePreviewed(file) && optionalUser.isPresent) { - //show image preview and stay in uploads tab + // show image preview and stay in uploads tab val intent = openFileIntent(parentActivity, optionalUser.get(), file) parentActivity.startActivity(intent) } else { @@ -932,15 +948,14 @@ class UploadListAdapter( * Open file with app associates with its MIME type. If MIME type unknown, show list with all apps. */ private fun openFileWithDefault(localPath: String) { - val myIntent = Intent(Intent.ACTION_VIEW) - val file = File(localPath) var mimetype = MimeTypeUtil.getBestMimeTypeByFilename(localPath) - if ("application/octet-stream" == mimetype) { - mimetype = "*/*" - } - myIntent.setDataAndType(Uri.fromFile(file), mimetype) + if (mimetype == "application/octet-stream") mimetype = "*/*" try { - parentActivity.startActivity(myIntent) + parentActivity.startActivity( + Intent(Intent.ACTION_VIEW).apply { + setDataAndType(Uri.fromFile(File(localPath)), mimetype) + } + ) } catch (e: ActivityNotFoundException) { DisplayUtils.showSnackMessage(parentActivity, R.string.file_list_no_app_for_file_type) Log_OC.i(TAG, "Could not find app for sending log history: $e") diff --git a/app/src/test/java/com/owncloud/android/ui/db/OCUploadComparatorTest.kt b/app/src/test/java/com/owncloud/android/ui/db/OCUploadSortingTest.kt similarity index 100% rename from app/src/test/java/com/owncloud/android/ui/db/OCUploadComparatorTest.kt rename to app/src/test/java/com/owncloud/android/ui/db/OCUploadSortingTest.kt From 1a11a9dfd853ec01e58de3b1229f6c664f0534b5 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 27 Mar 2026 15:54:37 +0100 Subject: [PATCH 05/11] refactor(upload-list-adapter)separate into data classes Signed-off-by: alperozturk96 --- .../extensions/UploadResultExtensions.kt | 71 +++++ .../ui/activity/UploadListActivity.java | 2 +- .../{ => uploadList}/UploadListAdapter.kt | 282 ++++++------------ .../adapter/uploadList/UploadListSection.kt | 63 ++++ .../ui/adapter/uploadList/UploadListType.kt | 10 + 5 files changed, 235 insertions(+), 193 deletions(-) rename app/src/main/java/com/owncloud/android/ui/adapter/{ => uploadList}/UploadListAdapter.kt (76%) mode change 100755 => 100644 create mode 100644 app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListSection.kt create mode 100644 app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListType.kt diff --git a/app/src/main/java/com/nextcloud/utils/extensions/UploadResultExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/UploadResultExtensions.kt index 9c1f1218e57a..0a9da25da2e9 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/UploadResultExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/UploadResultExtensions.kt @@ -7,6 +7,8 @@ package com.nextcloud.utils.extensions +import android.content.Context +import com.owncloud.android.R import com.owncloud.android.db.UploadResult fun UploadResult.isNonRetryable(): Boolean = when (this) { @@ -32,3 +34,72 @@ fun UploadResult.isNonRetryable(): Boolean = when (this) { // everything else may succeed after retry else -> false } + +fun UploadResult.getFailedStatusText(context: Context): String = when (this) { + UploadResult.CREDENTIAL_ERROR -> + context.getString(R.string.uploads_view_upload_status_failed_credentials_error) + + UploadResult.FOLDER_ERROR -> + context.getString(R.string.uploads_view_upload_status_failed_folder_error) + + UploadResult.FILE_NOT_FOUND -> + context.getString(R.string.uploads_view_upload_status_failed_localfile_error) + + UploadResult.FILE_ERROR -> context.getString(R.string.uploads_view_upload_status_failed_file_error) + + UploadResult.PRIVILEGES_ERROR -> context.getString( + R.string.uploads_view_upload_status_failed_permission_error + ) + + UploadResult.NETWORK_CONNECTION -> + context.getString(R.string.uploads_view_upload_status_failed_connection_error) + + UploadResult.DELAYED_FOR_WIFI -> context.getString( + R.string.uploads_view_upload_status_waiting_for_wifi + ) + + UploadResult.DELAYED_FOR_CHARGING -> + context.getString(R.string.uploads_view_upload_status_waiting_for_charging) + + UploadResult.CONFLICT_ERROR -> context.getString(R.string.uploads_view_upload_status_conflict) + + UploadResult.SERVICE_INTERRUPTED -> context.getString( + R.string.uploads_view_upload_status_service_interrupted + ) + + UploadResult.CANCELLED -> // should not get here ; cancelled uploads should be wiped out + context.getString(R.string.uploads_view_upload_status_cancelled) + + UploadResult.UPLOADED -> // should not get here ; status should be UPLOAD_SUCCESS + context.getString(R.string.uploads_view_upload_status_succeeded) + + UploadResult.MAINTENANCE_MODE -> context.getString(R.string.maintenance_mode) + + UploadResult.SSL_RECOVERABLE_PEER_UNVERIFIED -> context.getString( + R.string.uploads_view_upload_status_failed_ssl_certificate_not_trusted + ) + + UploadResult.UNKNOWN -> context.getString(R.string.uploads_view_upload_status_unknown_fail) + + UploadResult.LOCK_FAILED -> context.getString(R.string.upload_lock_failed) + + UploadResult.DELAYED_IN_POWER_SAVE_MODE -> context.getString( + R.string.uploads_view_upload_status_waiting_exit_power_save_mode + ) + + UploadResult.VIRUS_DETECTED -> context.getString(R.string.uploads_view_upload_status_virus_detected) + + UploadResult.LOCAL_STORAGE_FULL -> context.getString(R.string.upload_local_storage_full) + + UploadResult.OLD_ANDROID_API -> context.getString(R.string.upload_old_android) + + UploadResult.SYNC_CONFLICT -> context.getString(R.string.upload_sync_conflict) + + UploadResult.CANNOT_CREATE_FILE -> context.getString(R.string.upload_cannot_create_file) + + UploadResult.LOCAL_STORAGE_NOT_COPIED -> context.getString(R.string.upload_local_storage_not_copied) + + UploadResult.QUOTA_EXCEEDED -> context.getString(R.string.upload_quota_exceeded) + + else -> context.getString(R.string.upload_unknown_error) +} diff --git a/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java index 69893cabee31..69c079d10d9c 100755 --- a/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java @@ -37,7 +37,7 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.operations.CheckCurrentCredentialsOperation; -import com.owncloud.android.ui.adapter.UploadListAdapter; +import com.owncloud.android.ui.adapter.uploadList.UploadListAdapter; import com.owncloud.android.ui.decoration.MediaGridItemDecoration; import com.owncloud.android.utils.FilesSyncHelper; diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt old mode 100755 new mode 100644 similarity index 76% rename from app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.kt rename to app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt index a6446574c922..12071fa34b19 --- a/app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt @@ -4,7 +4,8 @@ * SPDX-FileCopyrightText: 2026 Alper Ozturk * SPDX-License-Identifier: AGPL-3.0-or-later */ -package com.owncloud.android.ui.adapter + +package com.owncloud.android.ui.adapter.uploadList import android.annotation.SuppressLint import android.app.NotificationManager @@ -25,10 +26,10 @@ import com.nextcloud.client.account.User import com.nextcloud.client.account.UserAccountManager import com.nextcloud.client.core.Clock import com.nextcloud.client.device.PowerManagementService -import com.nextcloud.client.jobs.upload.FileUploadHelper.Companion.buildRemoteName -import com.nextcloud.client.jobs.upload.FileUploadHelper.Companion.instance -import com.nextcloud.client.jobs.upload.FileUploadWorker.Companion.cancelUpload +import com.nextcloud.client.jobs.upload.FileUploadHelper +import com.nextcloud.client.jobs.upload.FileUploadWorker import com.nextcloud.client.network.ConnectivityService +import com.nextcloud.utils.extensions.getFailedStatusText import com.nextcloud.utils.extensions.setVisibleIf import com.nextcloud.utils.extensions.sortedByUploadOrder import com.owncloud.android.R @@ -37,11 +38,7 @@ import com.owncloud.android.databinding.UploadListItemBinding import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.datamodel.OCFile import com.owncloud.android.datamodel.ThumbnailsCacheManager -import com.owncloud.android.datamodel.ThumbnailsCacheManager.AsyncThumbnailDrawable -import com.owncloud.android.datamodel.ThumbnailsCacheManager.ThumbnailGenerationTask -import com.owncloud.android.datamodel.ThumbnailsCacheManager.ThumbnailGenerationTaskObject import com.owncloud.android.datamodel.UploadsStorageManager -import com.owncloud.android.datamodel.UploadsStorageManager.UploadStatus import com.owncloud.android.db.OCUpload import com.owncloud.android.db.UploadResult import com.owncloud.android.files.services.NameCollisionPolicy @@ -50,12 +47,11 @@ import com.owncloud.android.lib.common.operations.RemoteOperation import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.operations.RefreshFolderOperation -import com.owncloud.android.ui.activity.ConflictsResolveActivity.Companion.createIntent +import com.owncloud.android.ui.activity.ConflictsResolveActivity import com.owncloud.android.ui.activity.FileActivity import com.owncloud.android.ui.activity.FileDisplayActivity -import com.owncloud.android.ui.activity.FileDisplayActivity.Companion.openFileIntent import com.owncloud.android.ui.adapter.progressListener.UploadProgressListener -import com.owncloud.android.ui.preview.PreviewImageFragment.Companion.canBePreviewed +import com.owncloud.android.ui.preview.PreviewImageFragment import com.owncloud.android.utils.DisplayUtils import com.owncloud.android.utils.MimeTypeUtil import com.owncloud.android.utils.theme.ViewThemeUtils @@ -63,6 +59,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.File +import java.util.Optional import java.util.function.Consumer @Suppress("TooManyFunctions", "LargeClass", "LongParameterList", "NestedBlockDepth", "MaxLineLength", "ReturnCount") @@ -75,67 +72,13 @@ class UploadListAdapter( private val powerManagementService: PowerManagementService, private val clock: Clock, private val viewThemeUtils: ViewThemeUtils -) : SectionedRecyclerViewAdapter() { - - private data class Section( - val type: Type?, - val titleRes: Int, - val status: UploadStatus?, - val collisionPolicy: NameCollisionPolicy?, - val items: List - ) { - fun withItems(newItems: List) = copy(items = newItems) - } - - internal enum class Type { CURRENT, COMPLETED, FAILED, CANCELLED, SKIPPED } - - internal class HeaderViewHolder(val binding: UploadListHeaderBinding) : SectionedViewHolder(binding.root) +) : SectionedRecyclerViewAdapter() { - internal class ItemViewHolder(val binding: UploadListItemBinding) : SectionedViewHolder(binding.root) - - private val sections: MutableList
= ArrayList
( - listOf( - Section( - Type.CURRENT, - R.string.uploads_view_group_current_uploads, - UploadStatus.UPLOAD_IN_PROGRESS, - null, - listOf() - ), - Section( - Type.FAILED, - R.string.uploads_view_group_failed_uploads, - UploadStatus.UPLOAD_FAILED, - null, - listOf() - ), - Section( - Type.CANCELLED, - R.string.uploads_view_group_manually_cancelled_uploads, - UploadStatus.UPLOAD_CANCELLED, - null, - listOf() - ), - Section( - Type.COMPLETED, - R.string.uploads_view_group_completed_uploads, - UploadStatus.UPLOAD_SUCCEEDED, - NameCollisionPolicy.ASK_USER, - listOf() - ), - Section( - Type.SKIPPED, - R.string.uploads_view_upload_status_skip, - UploadStatus.UPLOAD_SUCCEEDED, - NameCollisionPolicy.SKIP, - listOf() - ) - ) - ) + private val uploadListSections = UploadListSection.sections() private val parentActivity: FileActivity = fileActivity private val showUser: Boolean = accountManager.getAccounts().size > 1 - private val uploadHelper = instance() + private val uploadHelper = FileUploadHelper.instance() private var uploadProgressListener: UploadProgressListener? = null private var mNotificationManager: NotificationManager? = null @@ -144,14 +87,18 @@ class UploadListAdapter( shouldShowHeadersForEmptySections(false) } - override fun getSectionCount(): Int = sections.size + internal class HeaderViewHolder(val binding: UploadListHeaderBinding) : SectionedViewHolder(binding.root) + + internal class ItemViewHolder(val binding: UploadListItemBinding) : SectionedViewHolder(binding.root) + + override fun getSectionCount(): Int = uploadListSections.size - override fun getItemCount(section: Int): Int = sections[section].items.size + override fun getItemCount(section: Int): Int = uploadListSections[section].items.size // region header - override fun onBindHeaderViewHolder(holder: SectionedViewHolder?, section: Int, expanded: Boolean) { + override fun onBindHeaderViewHolder(holder: SectionedViewHolder, section: Int, expanded: Boolean) { val headerViewHolder = holder as HeaderViewHolder - val group = sections[section] + val group = uploadListSections[section] bindHeaderTitle(headerViewHolder, group, section) bindHeaderActionButton(headerViewHolder, group) @@ -159,7 +106,7 @@ class UploadListAdapter( bindHeaderActionClickListener(headerViewHolder, group) } - private fun bindHeaderTitle(holder: HeaderViewHolder, group: Section, section: Int) { + private fun bindHeaderTitle(holder: HeaderViewHolder, group: UploadListSection, section: Int) { val title = parentActivity.getString(group.titleRes) val headerText = parentActivity.getString(R.string.uploads_view_group_header) holder.binding.uploadListTitle.text = String.format(headerText, title, group.items.size) @@ -174,10 +121,10 @@ class UploadListAdapter( holder.binding.uploadListStateLayout.setOnClickListener { toggleExpand() } } - private fun bindHeaderActionButton(holder: HeaderViewHolder, group: Section) { + private fun bindHeaderActionButton(holder: HeaderViewHolder, group: UploadListSection) { val iconRes = when (group.type) { - Type.CURRENT, Type.COMPLETED -> R.drawable.ic_close - Type.CANCELLED, Type.FAILED -> R.drawable.ic_dots_vertical + UploadListType.CURRENT, UploadListType.COMPLETED -> R.drawable.ic_close + UploadListType.CANCELLED, UploadListType.FAILED -> R.drawable.ic_dots_vertical else -> return } holder.binding.uploadListAction.setImageResource(iconRes) @@ -189,32 +136,36 @@ class UploadListAdapter( viewThemeUtils.material.themeCardView(holder.binding.autoUploadBatterySaverWarningCard.root) } - private fun bindHeaderActionClickListener(holder: HeaderViewHolder, group: Section) { + private fun bindHeaderActionClickListener(holder: HeaderViewHolder, group: UploadListSection) { holder.binding.uploadListAction.setOnClickListener { when (group.type) { - Type.CURRENT -> cancelAllCurrentUploads(group) + UploadListType.CURRENT -> cancelAllCurrentUploads(group) - Type.COMPLETED -> { + UploadListType.COMPLETED -> { uploadsStorageManager.clearSuccessfulUploads() loadUploadItemsFromDb {} } - Type.FAILED -> showFailedPopupMenu(holder) + UploadListType.FAILED -> showFailedPopupMenu(holder) - Type.CANCELLED -> showCancelledPopupMenu(holder) + UploadListType.CANCELLED -> showCancelledPopupMenu(holder) else -> {} } } } - private fun cancelAllCurrentUploads(group: Section) { + private fun cancelAllCurrentUploads(group: UploadListSection) { val items = group.items.takeIf { it.isNotEmpty() } ?: return val accountName = items[0].accountName var completedCount = 0 items.forEach { upload -> - uploadHelper.updateUploadStatus(upload.remotePath, accountName, UploadStatus.UPLOAD_CANCELLED) { - cancelUpload(upload.remotePath, accountName) { + uploadHelper.updateUploadStatus( + upload.remotePath, + accountName, + UploadsStorageManager.UploadStatus.UPLOAD_CANCELLED + ) { + FileUploadWorker.cancelUpload(upload.remotePath, accountName) { completedCount++ if (completedCount == items.size) { Log_OC.d(TAG, "refreshing upload items") @@ -303,8 +254,8 @@ class UploadListAdapter( relativePosition: Int, absolutePosition: Int ) { - if (sections.isEmpty() || section !in sections.indices) return - val item = sections[section].items[relativePosition] + if (uploadListSections.isEmpty() || section !in uploadListSections.indices) return + val item = uploadListSections[section].items[relativePosition] val itemViewHolder = holder as ItemViewHolder bindItemText(holder, item) @@ -343,7 +294,7 @@ class UploadListAdapter( private fun bindItemDate(holder: ItemViewHolder, item: OCUpload, updateTime: Long) { val showDate = ( updateTime > 0 && - item.uploadStatus == UploadStatus.UPLOAD_SUCCEEDED && + item.uploadStatus == UploadsStorageManager.UploadStatus.UPLOAD_SUCCEEDED && item.lastResult == UploadResult.UPLOADED ) @@ -383,17 +334,20 @@ class UploadListAdapter( val status = getStatusText(item) when (item.uploadStatus) { - UploadStatus.UPLOAD_IN_PROGRESS -> bindItemInProgress(holder, item) + UploadsStorageManager.UploadStatus.UPLOAD_IN_PROGRESS -> bindItemInProgress(holder, item) - UploadStatus.UPLOAD_SUCCEEDED, - UploadStatus.UPLOAD_CANCELLED -> uploadStatus.visibility = View.GONE + UploadsStorageManager.UploadStatus.UPLOAD_SUCCEEDED, + UploadsStorageManager.UploadStatus.UPLOAD_CANCELLED -> uploadStatus.visibility = View.GONE else -> {} } // Override visibility for edge cases - if ((item.uploadStatus == UploadStatus.UPLOAD_SUCCEEDED && item.lastResult != UploadResult.UPLOADED) || - item.uploadStatus == UploadStatus.UPLOAD_CANCELLED + if (( + item.uploadStatus == UploadsStorageManager.UploadStatus.UPLOAD_SUCCEEDED && + item.lastResult != UploadResult.UPLOADED + ) || + item.uploadStatus == UploadsStorageManager.UploadStatus.UPLOAD_CANCELLED ) { uploadStatus.visibility = View.VISIBLE uploadFileSize.visibility = View.GONE @@ -412,17 +366,17 @@ class UploadListAdapter( if (uploadHelper.isUploadingNow(item)) { uploadProgressListener?.upload?.let { prevUpload -> - val key = buildRemoteName(prevUpload.accountName, prevUpload.remotePath) + val key = FileUploadHelper.buildRemoteName(prevUpload.accountName, prevUpload.remotePath) uploadHelper.removeUploadTransferProgressListener(uploadProgressListener!!, key) } uploadProgressListener = UploadProgressListener(item, uploadProgressBar) uploadHelper.addUploadTransferProgressListener( uploadProgressListener!!, - buildRemoteName(item.accountName, item.remotePath) + FileUploadHelper.buildRemoteName(item.accountName, item.remotePath) ) } else if (uploadProgressListener?.isWrapping(uploadProgressBar) == true) { uploadProgressListener?.upload?.let { prevUpload -> - val key = buildRemoteName(prevUpload.accountName, prevUpload.remotePath) + val key = FileUploadHelper.buildRemoteName(prevUpload.accountName, prevUpload.remotePath) uploadHelper.removeUploadTransferProgressListener(uploadProgressListener!!, key) uploadProgressListener = null } @@ -439,7 +393,7 @@ class UploadListAdapter( // Right-side button when (item.uploadStatus) { - UploadStatus.UPLOAD_IN_PROGRESS -> { + UploadsStorageManager.UploadStatus.UPLOAD_IN_PROGRESS -> { uploadRightButton.run { setImageResource(R.drawable.ic_action_cancel_grey) visibility = View.VISIBLE @@ -447,15 +401,18 @@ class UploadListAdapter( uploadHelper.updateUploadStatus( item.remotePath, item.accountName, - UploadStatus.UPLOAD_CANCELLED + UploadsStorageManager.UploadStatus.UPLOAD_CANCELLED ) { - cancelUpload(item.remotePath, item.accountName) { loadUploadItemsFromDb {} } + FileUploadWorker.cancelUpload( + item.remotePath, + item.accountName + ) { loadUploadItemsFromDb {} } } } } } - UploadStatus.UPLOAD_FAILED -> { + UploadsStorageManager.UploadStatus.UPLOAD_FAILED -> { uploadRightButton.run { if (item.lastResult == UploadResult.SYNC_CONFLICT) { setImageResource(R.drawable.ic_dots_vertical) @@ -479,12 +436,13 @@ class UploadListAdapter( uploadListItemLayout.run { setOnClickListener(null) when (item.uploadStatus) { - UploadStatus.UPLOAD_FAILED, UploadStatus.UPLOAD_CANCELLED -> + UploadsStorageManager.UploadStatus.UPLOAD_FAILED, + UploadsStorageManager.UploadStatus.UPLOAD_CANCELLED -> setOnClickListener { onFailedOrCancelledItemClick(item, optionalUser, holder, status) } - UploadStatus.UPLOAD_SUCCEEDED -> + UploadsStorageManager.UploadStatus.UPLOAD_SUCCEEDED -> setOnClickListener { onUploadedItemClick(item) } else -> {} @@ -492,7 +450,7 @@ class UploadListAdapter( } // Thumbnail click to open locally - if (item.uploadStatus != UploadStatus.UPLOAD_SUCCEEDED) { + if (item.uploadStatus != UploadsStorageManager.UploadStatus.UPLOAD_SUCCEEDED) { thumbnail.setOnClickListener { onUploadingItemClick(item) } } } @@ -500,7 +458,7 @@ class UploadListAdapter( private fun onFailedOrCancelledItemClick( item: OCUpload, - optionalUser: java.util.Optional, + optionalUser: Optional, holder: ItemViewHolder, status: String ) { @@ -548,7 +506,7 @@ class UploadListAdapter( when { MimeTypeUtil.isImage(fakeFile) && fakeFile.remoteId != null && - item.uploadStatus == UploadStatus.UPLOAD_SUCCEEDED -> + item.uploadStatus == UploadsStorageManager.UploadStatus.UPLOAD_SUCCEEDED -> bindRemoteThumbnail(holder, item, fakeFile, allowedToCreateNewThumbnail) MimeTypeUtil.isImage(fakeFile) -> @@ -575,16 +533,20 @@ class UploadListAdapter( } else if (allowedToCreateNewThumbnail) { val user = parentActivity.user if (user.isPresent) { - val task = ThumbnailGenerationTask(holder.binding.thumbnail, parentActivity.storageManager, user.get()) + val task = ThumbnailsCacheManager.ThumbnailGenerationTask( + holder.binding.thumbnail, + parentActivity.storageManager, + user.get() + ) thumbnail = thumbnail ?: if (MimeTypeUtil.isVideo(fakeFile)) { ThumbnailsCacheManager.mDefaultVideo } else { ThumbnailsCacheManager.mDefaultImg } holder.binding.thumbnail.setImageDrawable( - AsyncThumbnailDrawable(parentActivity.resources, thumbnail, task) + ThumbnailsCacheManager.AsyncThumbnailDrawable(parentActivity.resources, thumbnail, task) ) - task.execute(ThumbnailGenerationTaskObject(fakeFile, null)) + task.execute(ThumbnailsCacheManager.ThumbnailGenerationTaskObject(fakeFile, null)) } } @@ -601,15 +563,16 @@ class UploadListAdapter( holder.binding.thumbnail.setImageBitmap(thumbnail) } else if (allowedToCreateNewThumbnail) { getThumbnailFromFileTypeAndSetIcon(item.localPath, holder) - val task = ThumbnailGenerationTask(holder.binding.thumbnail) + val task = ThumbnailsCacheManager.ThumbnailGenerationTask(holder.binding.thumbnail) val defaultThumbnail = if (MimeTypeUtil.isVideo(file)) { ThumbnailsCacheManager.mDefaultVideo } else { ThumbnailsCacheManager.mDefaultImg } - val asyncDrawable = AsyncThumbnailDrawable(parentActivity.resources, defaultThumbnail, task) - task.execute(ThumbnailGenerationTaskObject(file, null)) - task.setListener(object : ThumbnailGenerationTask.Listener { + val asyncDrawable = + ThumbnailsCacheManager.AsyncThumbnailDrawable(parentActivity.resources, defaultThumbnail, task) + task.execute(ThumbnailsCacheManager.ThumbnailGenerationTaskObject(file, null)) + task.setListener(object : ThumbnailsCacheManager.ThumbnailGenerationTask.Listener { override fun onSuccess() { holder.binding.thumbnail.setImageDrawable(asyncDrawable) } @@ -744,7 +707,7 @@ class UploadListAdapter( file.setStoragePath(upload.localPath) val user = accountManager.getUser(upload.accountName) if (user.isPresent) { - val intent = createIntent( + val intent = ConflictsResolveActivity.Companion.createIntent( file, user.get(), upload.uploadId, @@ -766,7 +729,7 @@ class UploadListAdapter( val res = parentActivity.getResources() val prefs = parentActivity.appPreferences when (val uploadStatus = upload.uploadStatus) { - UploadStatus.UPLOAD_IN_PROGRESS -> { + UploadsStorageManager.UploadStatus.UPLOAD_IN_PROGRESS -> { status = if (prefs.isGlobalUploadPaused()) { res.getString(R.string.upload_global_pause_title) } else if (uploadHelper.isUploadingNow(upload)) { @@ -776,12 +739,12 @@ class UploadListAdapter( } } - UploadStatus.UPLOAD_SUCCEEDED -> { + UploadsStorageManager.UploadStatus.UPLOAD_SUCCEEDED -> { val result = upload.lastResult status = if (result == UploadResult.SAME_FILE_CONFLICT) { res.getString(R.string.uploads_view_upload_status_succeeded_same_file) } else if (result == UploadResult.FILE_NOT_FOUND) { - getUploadFailedStatusText(result) + result.getFailedStatusText(fileActivity) } else if (upload.nameCollisionPolicy == NameCollisionPolicy.SKIP) { res.getString(R.string.uploads_view_upload_status_skip_reason) } else { @@ -789,9 +752,13 @@ class UploadListAdapter( } } - UploadStatus.UPLOAD_FAILED -> status = getUploadFailedStatusText(upload.lastResult) + UploadsStorageManager.UploadStatus.UPLOAD_FAILED -> + status = + upload.lastResult.getFailedStatusText(fileActivity) - UploadStatus.UPLOAD_CANCELLED -> status = res.getString(R.string.upload_manually_cancelled) + UploadsStorageManager.UploadStatus.UPLOAD_CANCELLED -> + status = + res.getString(R.string.upload_manually_cancelled) else -> status = "Uncontrolled status: $uploadStatus" } @@ -799,75 +766,6 @@ class UploadListAdapter( return status } - private fun getUploadFailedStatusText(result: UploadResult): String = when (result) { - UploadResult.CREDENTIAL_ERROR -> - parentActivity.getString(R.string.uploads_view_upload_status_failed_credentials_error) - - UploadResult.FOLDER_ERROR -> - parentActivity.getString(R.string.uploads_view_upload_status_failed_folder_error) - - UploadResult.FILE_NOT_FOUND -> - parentActivity.getString(R.string.uploads_view_upload_status_failed_localfile_error) - - UploadResult.FILE_ERROR -> parentActivity.getString(R.string.uploads_view_upload_status_failed_file_error) - - UploadResult.PRIVILEGES_ERROR -> parentActivity.getString( - R.string.uploads_view_upload_status_failed_permission_error - ) - - UploadResult.NETWORK_CONNECTION -> - parentActivity.getString(R.string.uploads_view_upload_status_failed_connection_error) - - UploadResult.DELAYED_FOR_WIFI -> parentActivity.getString( - R.string.uploads_view_upload_status_waiting_for_wifi - ) - - UploadResult.DELAYED_FOR_CHARGING -> - parentActivity.getString(R.string.uploads_view_upload_status_waiting_for_charging) - - UploadResult.CONFLICT_ERROR -> parentActivity.getString(R.string.uploads_view_upload_status_conflict) - - UploadResult.SERVICE_INTERRUPTED -> parentActivity.getString( - R.string.uploads_view_upload_status_service_interrupted - ) - - UploadResult.CANCELLED -> // should not get here ; cancelled uploads should be wiped out - parentActivity.getString(R.string.uploads_view_upload_status_cancelled) - - UploadResult.UPLOADED -> // should not get here ; status should be UPLOAD_SUCCESS - parentActivity.getString(R.string.uploads_view_upload_status_succeeded) - - UploadResult.MAINTENANCE_MODE -> parentActivity.getString(R.string.maintenance_mode) - - UploadResult.SSL_RECOVERABLE_PEER_UNVERIFIED -> parentActivity.getString( - R.string.uploads_view_upload_status_failed_ssl_certificate_not_trusted - ) - - UploadResult.UNKNOWN -> parentActivity.getString(R.string.uploads_view_upload_status_unknown_fail) - - UploadResult.LOCK_FAILED -> parentActivity.getString(R.string.upload_lock_failed) - - UploadResult.DELAYED_IN_POWER_SAVE_MODE -> parentActivity.getString( - R.string.uploads_view_upload_status_waiting_exit_power_save_mode - ) - - UploadResult.VIRUS_DETECTED -> parentActivity.getString(R.string.uploads_view_upload_status_virus_detected) - - UploadResult.LOCAL_STORAGE_FULL -> parentActivity.getString(R.string.upload_local_storage_full) - - UploadResult.OLD_ANDROID_API -> parentActivity.getString(R.string.upload_old_android) - - UploadResult.SYNC_CONFLICT -> parentActivity.getString(R.string.upload_sync_conflict) - - UploadResult.CANNOT_CREATE_FILE -> parentActivity.getString(R.string.upload_cannot_create_file) - - UploadResult.LOCAL_STORAGE_NOT_COPIED -> parentActivity.getString(R.string.upload_local_storage_not_copied) - - UploadResult.QUOTA_EXCEEDED -> parentActivity.getString(R.string.upload_quota_exceeded) - - else -> parentActivity.getString(R.string.upload_unknown_error) - } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SectionedViewHolder = if (viewType == VIEW_TYPE_HEADER) { HeaderViewHolder( @@ -887,8 +785,8 @@ class UploadListAdapter( if (!optionalCapabilities.isPresent) return@ifPresent val capabilities = optionalCapabilities.get() - sections.indices.forEach { i -> - val sec = sections[i] + uploadListSections.indices.forEach { i -> + val sec = uploadListSections[i] uploadHelper.getUploadsByStatus( accountName, sec.status!!, @@ -896,7 +794,7 @@ class UploadListAdapter( sec.collisionPolicy ) { uploads -> uploads.forEach { it.setDataFixed(uploadHelper) } - sections[i] = sec.withItems(uploads.sortedByUploadOrder()) + uploadListSections[i] = sec.withItems(uploads.sortedByUploadOrder()) parentActivity.runOnUiThread { notifyDataSetChanged() onCompleted.run() @@ -931,14 +829,14 @@ class UploadListAdapter( val optionalUser = parentActivity.user - if (canBePreviewed(file) && optionalUser.isPresent) { + if (PreviewImageFragment.canBePreviewed(file) && optionalUser.isPresent) { // show image preview and stay in uploads tab - val intent = openFileIntent(parentActivity, optionalUser.get(), file) + val intent = FileDisplayActivity.openFileIntent(parentActivity, optionalUser.get(), file) parentActivity.startActivity(intent) } else { val intent = Intent(parentActivity, FileDisplayActivity::class.java) intent.setAction(Intent.ACTION_VIEW) - intent.putExtra(FileDisplayActivity.KEY_FILE_PATH, upload.remotePath) + intent.putExtra(FileDisplayActivity.Companion.KEY_FILE_PATH, upload.remotePath) intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) parentActivity.startActivity(intent) } diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListSection.kt b/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListSection.kt new file mode 100644 index 000000000000..43ded6738632 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListSection.kt @@ -0,0 +1,63 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.ui.adapter.uploadList + +import com.owncloud.android.R +import com.owncloud.android.datamodel.UploadsStorageManager +import com.owncloud.android.db.OCUpload +import com.owncloud.android.files.services.NameCollisionPolicy + +data class UploadListSection( + val type: UploadListType?, + val titleRes: Int, + val status: UploadsStorageManager.UploadStatus?, + val collisionPolicy: NameCollisionPolicy?, + val items: List +) { + fun withItems(newItems: List) = copy(items = newItems) + + companion object { + fun sections(): MutableList = mutableListOf( + UploadListSection( + UploadListType.CURRENT, + R.string.uploads_view_group_current_uploads, + UploadsStorageManager.UploadStatus.UPLOAD_IN_PROGRESS, + null, + listOf() + ), + UploadListSection( + UploadListType.FAILED, + R.string.uploads_view_group_failed_uploads, + UploadsStorageManager.UploadStatus.UPLOAD_FAILED, + null, + listOf() + ), + UploadListSection( + UploadListType.CANCELLED, + R.string.uploads_view_group_manually_cancelled_uploads, + UploadsStorageManager.UploadStatus.UPLOAD_CANCELLED, + null, + listOf() + ), + UploadListSection( + UploadListType.COMPLETED, + R.string.uploads_view_group_completed_uploads, + UploadsStorageManager.UploadStatus.UPLOAD_SUCCEEDED, + NameCollisionPolicy.ASK_USER, + listOf() + ), + UploadListSection( + UploadListType.SKIPPED, + R.string.uploads_view_upload_status_skip, + UploadsStorageManager.UploadStatus.UPLOAD_SUCCEEDED, + NameCollisionPolicy.SKIP, + listOf() + ) + ) + } +} diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListType.kt b/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListType.kt new file mode 100644 index 000000000000..aa2d818f9378 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListType.kt @@ -0,0 +1,10 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.ui.adapter.uploadList + +enum class UploadListType { CURRENT, COMPLETED, FAILED, CANCELLED, SKIPPED } From 2eedb4087105387d93ad5a00df8ef21f9a2471ab Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 27 Mar 2026 16:08:05 +0100 Subject: [PATCH 06/11] refactor(upload-list-adapter)use helper classes Signed-off-by: alperozturk96 --- .../utils/extensions/OCUploadExtensions.kt | 46 ++++ .../android/ui/activity/FileActivity.java | 2 +- .../adapter/uploadList/UploadListAdapter.kt | 254 +++++------------- .../uploadList/UploadListAdapterHelper.kt | 93 +++++++ 4 files changed, 214 insertions(+), 181 deletions(-) create mode 100644 app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapterHelper.kt diff --git a/app/src/main/java/com/nextcloud/utils/extensions/OCUploadExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/OCUploadExtensions.kt index 933536db1ff2..df2b1c68059d 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/OCUploadExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/OCUploadExtensions.kt @@ -7,7 +7,13 @@ package com.nextcloud.utils.extensions +import android.content.Context +import com.nextcloud.client.preferences.AppPreferences +import com.owncloud.android.R +import com.owncloud.android.datamodel.UploadsStorageManager import com.owncloud.android.db.OCUpload +import com.owncloud.android.db.UploadResult +import com.owncloud.android.files.services.NameCollisionPolicy fun List.getUploadIds(): LongArray = map { it.uploadId }.toLongArray() @@ -19,3 +25,43 @@ fun List.sortedByUploadOrder(): List = sortedWith( .thenByDescending { it.fixedUploadEndTimeStamp } .thenBy { it.fixedUploadId } ) + +fun OCUpload.getStatusText(activity: Context, isGlobalUploadPaused: Boolean, isUploading: Boolean): String { + val status: String + val res = activity.resources + when (val uploadStatus = uploadStatus) { + UploadsStorageManager.UploadStatus.UPLOAD_IN_PROGRESS -> { + status = if (isGlobalUploadPaused) { + res.getString(R.string.upload_global_pause_title) + } else if (isUploading) { + res.getString(R.string.uploader_upload_in_progress_ticker) + } else { + res.getString(R.string.uploads_view_later_waiting_to_upload) + } + } + + UploadsStorageManager.UploadStatus.UPLOAD_SUCCEEDED -> { + status = if (lastResult == UploadResult.SAME_FILE_CONFLICT) { + res.getString(R.string.uploads_view_upload_status_succeeded_same_file) + } else if (lastResult == UploadResult.FILE_NOT_FOUND) { + lastResult.getFailedStatusText(activity) + } else if (nameCollisionPolicy == NameCollisionPolicy.SKIP) { + res.getString(R.string.uploads_view_upload_status_skip_reason) + } else { + res.getString(R.string.uploads_view_upload_status_succeeded) + } + } + + UploadsStorageManager.UploadStatus.UPLOAD_FAILED -> + status = + lastResult.getFailedStatusText(activity) + + UploadsStorageManager.UploadStatus.UPLOAD_CANCELLED -> + status = + res.getString(R.string.upload_manually_cancelled) + + else -> status = "Uncontrolled status: $uploadStatus" + } + + return status +} diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java index 280acaf45bad..ec834e983c1d 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java @@ -175,7 +175,7 @@ public abstract class FileActivity extends DrawerActivity protected boolean isFileDisplayActivityResumed = false; @Inject - UserAccountManager accountManager; + public UserAccountManager accountManager; @Inject public ConnectivityService connectivityService; diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt index 12071fa34b19..bbd9b886dd19 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt @@ -9,10 +9,7 @@ package com.owncloud.android.ui.adapter.uploadList import android.annotation.SuppressLint import android.app.NotificationManager -import android.content.ActivityNotFoundException import android.content.Context -import android.content.Intent -import android.net.Uri import android.text.format.DateUtils import android.view.LayoutInflater import android.view.View @@ -29,7 +26,7 @@ import com.nextcloud.client.device.PowerManagementService import com.nextcloud.client.jobs.upload.FileUploadHelper import com.nextcloud.client.jobs.upload.FileUploadWorker import com.nextcloud.client.network.ConnectivityService -import com.nextcloud.utils.extensions.getFailedStatusText +import com.nextcloud.utils.extensions.getStatusText import com.nextcloud.utils.extensions.setVisibleIf import com.nextcloud.utils.extensions.sortedByUploadOrder import com.owncloud.android.R @@ -41,17 +38,13 @@ import com.owncloud.android.datamodel.ThumbnailsCacheManager import com.owncloud.android.datamodel.UploadsStorageManager import com.owncloud.android.db.OCUpload import com.owncloud.android.db.UploadResult -import com.owncloud.android.files.services.NameCollisionPolicy import com.owncloud.android.lib.common.operations.OnRemoteOperationListener import com.owncloud.android.lib.common.operations.RemoteOperation import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.operations.RefreshFolderOperation -import com.owncloud.android.ui.activity.ConflictsResolveActivity import com.owncloud.android.ui.activity.FileActivity -import com.owncloud.android.ui.activity.FileDisplayActivity import com.owncloud.android.ui.adapter.progressListener.UploadProgressListener -import com.owncloud.android.ui.preview.PreviewImageFragment import com.owncloud.android.utils.DisplayUtils import com.owncloud.android.utils.MimeTypeUtil import com.owncloud.android.utils.theme.ViewThemeUtils @@ -62,9 +55,17 @@ import java.io.File import java.util.Optional import java.util.function.Consumer -@Suppress("TooManyFunctions", "LargeClass", "LongParameterList", "NestedBlockDepth", "MaxLineLength", "ReturnCount") +@Suppress( + "LongMethod", + "TooManyFunctions", + "LargeClass", + "LongParameterList", + "NestedBlockDepth", + "MaxLineLength", + "ReturnCount" +) class UploadListAdapter( - private val fileActivity: FileActivity, + private val activity: FileActivity, private val uploadsStorageManager: UploadsStorageManager, private val storageManager: FileDataStorageManager, private val accountManager: UserAccountManager, @@ -75,12 +76,11 @@ class UploadListAdapter( ) : SectionedRecyclerViewAdapter() { private val uploadListSections = UploadListSection.sections() - - private val parentActivity: FileActivity = fileActivity private val showUser: Boolean = accountManager.getAccounts().size > 1 private val uploadHelper = FileUploadHelper.instance() private var uploadProgressListener: UploadProgressListener? = null - private var mNotificationManager: NotificationManager? = null + private var notificationManager: NotificationManager? = null + private val helper = UploadListAdapterHelper(activity) init { Log_OC.d(TAG, "UploadListAdapter") @@ -107,8 +107,8 @@ class UploadListAdapter( } private fun bindHeaderTitle(holder: HeaderViewHolder, group: UploadListSection, section: Int) { - val title = parentActivity.getString(group.titleRes) - val headerText = parentActivity.getString(R.string.uploads_view_group_header) + val title = activity.getString(group.titleRes) + val headerText = activity.getString(R.string.uploads_view_group_header) holder.binding.uploadListTitle.text = String.format(headerText, title, group.items.size) viewThemeUtils.platform.colorTextView(holder.binding.uploadListTitle) @@ -177,7 +177,7 @@ class UploadListAdapter( } private fun showFailedPopupMenu(holder: HeaderViewHolder) { - PopupMenu(fileActivity, holder.binding.uploadListAction).apply { + PopupMenu(activity, holder.binding.uploadListAction).apply { inflate(R.menu.upload_list_failed_options) setOnMenuItemClickListener { item -> when (item.itemId) { @@ -202,7 +202,7 @@ class UploadListAdapter( } private fun showCancelledPopupMenu(holder: HeaderViewHolder) { - PopupMenu(fileActivity, holder.binding.uploadListAction).apply { + PopupMenu(activity, holder.binding.uploadListAction).apply { inflate(R.menu.upload_list_cancelled_options) setOnMenuItemClickListener { item -> when (item.itemId) { @@ -221,7 +221,7 @@ class UploadListAdapter( } private fun clearTempEncryptedFolder() { - val user = parentActivity.user + val user = activity.user user.ifPresent( Consumer { value: User? -> FileDataStorageManager.clearTempEncryptedFolder(value!!.accountName) @@ -231,7 +231,7 @@ class UploadListAdapter( // FIXME For e2e resume is not working private fun retryCancelledUploads() { - fileActivity.lifecycleScope.launch(Dispatchers.IO) { + activity.lifecycleScope.launch(Dispatchers.IO) { val showNotExistMessage = uploadHelper.retryCancelledUploads( uploadsStorageManager, connectivityService, @@ -240,7 +240,7 @@ class UploadListAdapter( ) if (showNotExistMessage) { withContext(Dispatchers.Main) { - DisplayUtils.showSnackMessage(parentActivity, R.string.upload_action_file_not_exist_message) + DisplayUtils.showSnackMessage(activity, R.string.upload_action_file_not_exist_message) } } } @@ -302,7 +302,7 @@ class UploadListAdapter( if (showDate) { holder.binding.uploadDate.text = DisplayUtils.getRelativeDateTimeString( - parentActivity, + activity, updateTime, DateUtils.MINUTE_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, @@ -332,7 +332,11 @@ class UploadListAdapter( uploadStatus.visibility = View.VISIBLE uploadProgressBar.visibility = View.GONE - val status = getStatusText(item) + val status = item.getStatusText( + activity, + activity.appPreferences.isGlobalUploadPaused, + uploadHelper.isUploadingNow(item) + ) when (item.uploadStatus) { UploadsStorageManager.UploadStatus.UPLOAD_IN_PROGRESS -> bindItemInProgress(holder, item) @@ -389,7 +393,11 @@ class UploadListAdapter( private fun bindItemActions(holder: ItemViewHolder, item: OCUpload) { holder.binding.run { val optionalUser = accountManager.getUser(item.accountName) - val status = getStatusText(item) + val status = item.getStatusText( + activity, + activity.appPreferences.isGlobalUploadPaused, + uploadHelper.isUploadingNow(item) + ) // Right-side button when (item.uploadStatus) { @@ -443,7 +451,7 @@ class UploadListAdapter( } UploadsStorageManager.UploadStatus.UPLOAD_SUCCEEDED -> - setOnClickListener { onUploadedItemClick(item) } + setOnClickListener { helper.onUploadedItemClick(item) } else -> {} } @@ -451,7 +459,7 @@ class UploadListAdapter( // Thumbnail click to open locally if (item.uploadStatus != UploadsStorageManager.UploadStatus.UPLOAD_SUCCEEDED) { - thumbnail.setOnClickListener { onUploadingItemClick(item) } + thumbnail.setOnClickListener { helper.onUploadingItemClick(item) } } } } @@ -465,7 +473,7 @@ class UploadListAdapter( when (item.lastResult) { UploadResult.CREDENTIAL_ERROR -> { val user = optionalUser.orElseThrow { RuntimeException() } - parentActivity.fileOperationsHelper.checkCurrentCredentials(user) + activity.fileOperationsHelper.checkCurrentCredentials(user) } UploadResult.SYNC_CONFLICT if optionalUser.isPresent -> { @@ -484,7 +492,7 @@ class UploadListAdapter( uploadHelper.retryUpload(item, user.get()) } else { DisplayUtils.showSnackMessage( - fileActivity, + activity, R.string.local_file_not_found_message ) } @@ -513,7 +521,7 @@ class UploadListAdapter( bindLocalThumbnail(holder, item, allowedToCreateNewThumbnail) optionalUser.isPresent -> { - val icon = MimeTypeUtil.getFileTypeIcon(item.mimeType, fileName, parentActivity, viewThemeUtils) + val icon = MimeTypeUtil.getFileTypeIcon(item.mimeType, fileName, activity, viewThemeUtils) holder.binding.thumbnail.setImageDrawable(icon) } } @@ -531,11 +539,11 @@ class UploadListAdapter( if (thumbnail != null && !fakeFile.isUpdateThumbnailNeeded) { holder.binding.thumbnail.setImageBitmap(thumbnail) } else if (allowedToCreateNewThumbnail) { - val user = parentActivity.user + val user = activity.user if (user.isPresent) { val task = ThumbnailsCacheManager.ThumbnailGenerationTask( holder.binding.thumbnail, - parentActivity.storageManager, + activity.storageManager, user.get() ) thumbnail = thumbnail ?: if (MimeTypeUtil.isVideo(fakeFile)) { @@ -544,14 +552,14 @@ class UploadListAdapter( ThumbnailsCacheManager.mDefaultImg } holder.binding.thumbnail.setImageDrawable( - ThumbnailsCacheManager.AsyncThumbnailDrawable(parentActivity.resources, thumbnail, task) + ThumbnailsCacheManager.AsyncThumbnailDrawable(activity.resources, thumbnail, task) ) task.execute(ThumbnailsCacheManager.ThumbnailGenerationTaskObject(fakeFile, null)) } } if (item.mimeType == "image/png") { - holder.binding.thumbnail.setBackgroundColor(ContextCompat.getColor(parentActivity, R.color.bg_default)) + holder.binding.thumbnail.setBackgroundColor(ContextCompat.getColor(activity, R.color.bg_default)) } } @@ -570,7 +578,7 @@ class UploadListAdapter( ThumbnailsCacheManager.mDefaultImg } val asyncDrawable = - ThumbnailsCacheManager.AsyncThumbnailDrawable(parentActivity.resources, defaultThumbnail, task) + ThumbnailsCacheManager.AsyncThumbnailDrawable(activity.resources, defaultThumbnail, task) task.execute(ThumbnailsCacheManager.ThumbnailGenerationTaskObject(file, null)) task.setListener(object : ThumbnailsCacheManager.ThumbnailGenerationTask.Listener { override fun onSuccess() { @@ -585,7 +593,7 @@ class UploadListAdapter( } if (item.mimeType.equals("image/png", ignoreCase = true)) { - holder.binding.thumbnail.setBackgroundColor(ContextCompat.getColor(parentActivity, R.color.bg_default)) + holder.binding.thumbnail.setBackgroundColor(ContextCompat.getColor(activity, R.color.bg_default)) } } // endregion @@ -593,7 +601,7 @@ class UploadListAdapter( override fun onBindFooterViewHolder(holder: SectionedViewHolder?, section: Int) = Unit private fun getThumbnailFromFileTypeAndSetIcon(localPath: String?, itemViewHolder: ItemViewHolder) { - val drawable = MimeTypeUtil.getIcon(localPath, parentActivity, viewThemeUtils) ?: return + val drawable = MimeTypeUtil.getIcon(localPath, activity, viewThemeUtils) ?: return itemViewHolder.binding.thumbnail.setImageDrawable(drawable) } @@ -619,7 +627,7 @@ class UploadListAdapter( } if (localFile != null) { - this.openConflictActivity(localFile, item) + helper.openConflictActivity(localFile, item) return true } @@ -644,9 +652,9 @@ class UploadListAdapter( if (result?.isSuccess == true) { val fileOnServer = storageManager.getFileByEncryptedRemotePath(remotePath) if (fileOnServer != null) { - openConflictActivity(fileOnServer, item) + helper.openConflictActivity(fileOnServer, item) } else { - displayFileNotFoundError(holder.itemView, fileActivity) + displayFileNotFoundError(holder.itemView, activity) } } } @@ -664,7 +672,7 @@ class UploadListAdapter( status: String?, view: View? ) { - PopupMenu(fileActivity, view).apply { + PopupMenu(activity, view).apply { inflate(R.menu.upload_list_item_file_conflict) setOnMenuItemClickListener { menuItem -> if (menuItem.itemId == R.id.action_upload_list_resolve_conflict) { @@ -695,75 +703,12 @@ class UploadListAdapter( true, storageManager, user, - fileActivity + activity ) - .execute(user, fileActivity, { caller, result -> + .execute(user, activity, { caller, result -> view.binding.uploadListItemLayout.isClickable = true listener.onRemoteOperationFinish(caller, result) - }, parentActivity.handler) - } - - private fun openConflictActivity(file: OCFile, upload: OCUpload) { - file.setStoragePath(upload.localPath) - val user = accountManager.getUser(upload.accountName) - if (user.isPresent) { - val intent = ConflictsResolveActivity.Companion.createIntent( - file, - user.get(), - upload.uploadId, - Intent.FLAG_ACTIVITY_NEW_TASK, - fileActivity - ) - fileActivity.startActivity(intent) - } - } - - /** - * Gets the status text to show to the user according to the status and last result of the the given upload. - * - * @param upload Upload to describe. - * @return Text describing the status of the given upload. - */ - private fun getStatusText(upload: OCUpload): String { - val status: String - val res = parentActivity.getResources() - val prefs = parentActivity.appPreferences - when (val uploadStatus = upload.uploadStatus) { - UploadsStorageManager.UploadStatus.UPLOAD_IN_PROGRESS -> { - status = if (prefs.isGlobalUploadPaused()) { - res.getString(R.string.upload_global_pause_title) - } else if (uploadHelper.isUploadingNow(upload)) { - res.getString(R.string.uploader_upload_in_progress_ticker) - } else { - res.getString(R.string.uploads_view_later_waiting_to_upload) - } - } - - UploadsStorageManager.UploadStatus.UPLOAD_SUCCEEDED -> { - val result = upload.lastResult - status = if (result == UploadResult.SAME_FILE_CONFLICT) { - res.getString(R.string.uploads_view_upload_status_succeeded_same_file) - } else if (result == UploadResult.FILE_NOT_FOUND) { - result.getFailedStatusText(fileActivity) - } else if (upload.nameCollisionPolicy == NameCollisionPolicy.SKIP) { - res.getString(R.string.uploads_view_upload_status_skip_reason) - } else { - res.getString(R.string.uploads_view_upload_status_succeeded) - } - } - - UploadsStorageManager.UploadStatus.UPLOAD_FAILED -> - status = - upload.lastResult.getFailedStatusText(fileActivity) - - UploadsStorageManager.UploadStatus.UPLOAD_CANCELLED -> - status = - res.getString(R.string.upload_manually_cancelled) - - else -> status = "Uncontrolled status: $uploadStatus" - } - - return status + }, activity.handler) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SectionedViewHolder = @@ -779,97 +724,46 @@ class UploadListAdapter( @SuppressLint("NotifyDataSetChanged") fun loadUploadItemsFromDb(onCompleted: Runnable) { - parentActivity.user.ifPresent { user -> - val accountName = user.accountName - val optionalCapabilities = parentActivity.capabilities - if (!optionalCapabilities.isPresent) return@ifPresent - val capabilities = optionalCapabilities.get() - - uploadListSections.indices.forEach { i -> - val sec = uploadListSections[i] - uploadHelper.getUploadsByStatus( - accountName, - sec.status!!, - capabilities, - sec.collisionPolicy - ) { uploads -> - uploads.forEach { it.setDataFixed(uploadHelper) } - uploadListSections[i] = sec.withItems(uploads.sortedByUploadOrder()) - parentActivity.runOnUiThread { - notifyDataSetChanged() - onCompleted.run() - } - } - } - } - } - - /** - * Open local file. - */ - private fun onUploadingItemClick(file: OCUpload) { - val f = File(file.localPath) - if (!f.exists()) { - DisplayUtils.showSnackMessage(parentActivity, R.string.local_file_not_found_message) - } else { - openFileWithDefault(file.localPath) - } - } - - /** - * Open remote file. - */ - private fun onUploadedItemClick(upload: OCUpload) { - val file = parentActivity.storageManager.getFileByEncryptedRemotePath(upload.remotePath) - if (file == null) { - DisplayUtils.showSnackMessage(parentActivity, R.string.error_retrieving_file) - Log_OC.i(TAG, "Could not find uploaded file on remote.") + val optionalUser = activity.user + if (optionalUser.isEmpty) { return } + val accountName = optionalUser.get().accountName - val optionalUser = parentActivity.user - - if (PreviewImageFragment.canBePreviewed(file) && optionalUser.isPresent) { - // show image preview and stay in uploads tab - val intent = FileDisplayActivity.openFileIntent(parentActivity, optionalUser.get(), file) - parentActivity.startActivity(intent) - } else { - val intent = Intent(parentActivity, FileDisplayActivity::class.java) - intent.setAction(Intent.ACTION_VIEW) - intent.putExtra(FileDisplayActivity.Companion.KEY_FILE_PATH, upload.remotePath) - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - parentActivity.startActivity(intent) + val optionalCapabilities = activity.capabilities + if (optionalCapabilities.isEmpty) { + return } - } + val capabilities = optionalCapabilities.get() - /** - * Open file with app associates with its MIME type. If MIME type unknown, show list with all apps. - */ - private fun openFileWithDefault(localPath: String) { - var mimetype = MimeTypeUtil.getBestMimeTypeByFilename(localPath) - if (mimetype == "application/octet-stream") mimetype = "*/*" - try { - parentActivity.startActivity( - Intent(Intent.ACTION_VIEW).apply { - setDataAndType(Uri.fromFile(File(localPath)), mimetype) + uploadListSections.indices.forEach { i -> + val sec = uploadListSections[i] + uploadHelper.getUploadsByStatus( + accountName, + sec.status!!, + capabilities, + sec.collisionPolicy + ) { uploads -> + uploads.forEach { it.setDataFixed(uploadHelper) } + uploadListSections[i] = sec.withItems(uploads.sortedByUploadOrder()) + activity.runOnUiThread { + notifyDataSetChanged() + onCompleted.run() } - ) - } catch (e: ActivityNotFoundException) { - DisplayUtils.showSnackMessage(parentActivity, R.string.file_list_no_app_for_file_type) - Log_OC.i(TAG, "Could not find app for sending log history: $e") + } } } fun cancelOldErrorNotification(upload: OCUpload?) { - if (mNotificationManager == null) { - mNotificationManager = parentActivity.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager? + if (notificationManager == null) { + notificationManager = activity.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager? } if (upload == null) { return } - mNotificationManager!!.cancel(upload.uploadId.toInt()) + notificationManager?.cancel(upload.uploadId.toInt()) } companion object { diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapterHelper.kt b/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapterHelper.kt new file mode 100644 index 000000000000..64cd8edb56c0 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapterHelper.kt @@ -0,0 +1,93 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.ui.adapter.uploadList + +import android.content.ActivityNotFoundException +import android.content.Intent +import android.net.Uri +import com.owncloud.android.R +import com.owncloud.android.datamodel.OCFile +import com.owncloud.android.db.OCUpload +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.ui.activity.ConflictsResolveActivity +import com.owncloud.android.ui.activity.FileActivity +import com.owncloud.android.ui.activity.FileDisplayActivity +import com.owncloud.android.ui.preview.PreviewImageFragment +import com.owncloud.android.utils.DisplayUtils +import com.owncloud.android.utils.MimeTypeUtil +import java.io.File + +class UploadListAdapterHelper(private val activity: FileActivity) { + + companion object { + private const val TAG = "UploadListAdapterHelper" + } + + fun openConflictActivity(file: OCFile, upload: OCUpload) { + file.setStoragePath(upload.localPath) + val user = activity.accountManager.getUser(upload.accountName) + if (user.isPresent) { + val intent = ConflictsResolveActivity.createIntent( + file, + user.get(), + upload.uploadId, + Intent.FLAG_ACTIVITY_NEW_TASK, + activity + ) + activity.startActivity(intent) + } + } + + fun onUploadingItemClick(file: OCUpload) { + val f = File(file.localPath) + if (!f.exists()) { + DisplayUtils.showSnackMessage(activity, R.string.local_file_not_found_message) + } else { + openFileWithDefault(file.localPath) + } + } + + fun onUploadedItemClick(upload: OCUpload) { + val file = activity.storageManager.getFileByEncryptedRemotePath(upload.remotePath) + if (file == null) { + DisplayUtils.showSnackMessage(activity, R.string.error_retrieving_file) + Log_OC.i(TAG, "Could not find uploaded file on remote.") + return + } + + val optionalUser = activity.user + if (PreviewImageFragment.canBePreviewed(file) && optionalUser.isPresent) { + // show image preview and stay in uploads tab + val intent = FileDisplayActivity.openFileIntent(activity, optionalUser.get(), file) + activity.startActivity(intent) + return + } + + val intent = Intent(activity, FileDisplayActivity::class.java).apply { + setAction(Intent.ACTION_VIEW) + putExtra(FileDisplayActivity.KEY_FILE_PATH, upload.remotePath) + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + } + activity.startActivity(intent) + } + + fun openFileWithDefault(localPath: String) { + var mimetype = MimeTypeUtil.getBestMimeTypeByFilename(localPath) + if (mimetype == "application/octet-stream") mimetype = "*/*" + try { + activity.startActivity( + Intent(Intent.ACTION_VIEW).apply { + setDataAndType(Uri.fromFile(File(localPath)), mimetype) + } + ) + } catch (e: ActivityNotFoundException) { + DisplayUtils.showSnackMessage(activity, R.string.file_list_no_app_for_file_type) + Log_OC.i(TAG, "Could not find app for sending log history: $e") + } + } +} From 054474c0364a9cdd601f481ddcbe5c540255a205 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 27 Mar 2026 16:14:21 +0100 Subject: [PATCH 07/11] refactor(upload-list-adapter)make suspended getUploadsByStatus Signed-off-by: alperozturk96 --- .../client/jobs/upload/FileUploadHelper.kt | 54 ++++++++----------- .../ui/activity/UploadListActivity.java | 2 +- .../adapter/uploadList/UploadListAdapter.kt | 32 +++++------ 3 files changed, 41 insertions(+), 47 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt index f8ebfe9a97de..0a3477242ab0 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt @@ -124,8 +124,9 @@ class FileUploadHelper { val capability = fileStorageManager.getCapability(accountManager.user) try { - getUploadsByStatus(null, UploadStatus.UPLOAD_FAILED, capability) { - if (it.isNotEmpty()) { + ioScope.launch { + val uploads = getUploadsByStatus(null, UploadStatus.UPLOAD_FAILED, capability) + if (uploads.isNotEmpty()) { isUploadStarted = true } @@ -134,7 +135,7 @@ class FileUploadHelper { connectivityService, accountManager, powerManagementService, - uploads = it + uploads ) } } finally { @@ -144,26 +145,21 @@ class FileUploadHelper { return isUploadStarted } - fun retryCancelledUploads( + suspend fun retryCancelledUploads( uploadsStorageManager: UploadsStorageManager, connectivityService: ConnectivityService, accountManager: UserAccountManager, powerManagementService: PowerManagementService ): Boolean { - var result = false val capability = fileStorageManager.getCapability(accountManager.user) - - getUploadsByStatus(accountManager.user.accountName, UploadStatus.UPLOAD_CANCELLED, capability) { - result = retryUploads( - uploadsStorageManager, - connectivityService, - accountManager, - powerManagementService, - it - ) - } - - return result + val uploads = getUploadsByStatus(accountManager.user.accountName, UploadStatus.UPLOAD_CANCELLED, capability) + return retryUploads( + uploadsStorageManager, + connectivityService, + accountManager, + powerManagementService, + uploads + ) } @Suppress("ComplexCondition") @@ -351,23 +347,19 @@ class FileUploadHelper { * @param nameCollisionPolicy The [NameCollisionPolicy] to filter uploads by (e.g., `SKIP`). * @param onCompleted A callback invoked with the resulting array of [OCUpload] objects. */ - fun getUploadsByStatus( + suspend fun getUploadsByStatus( accountName: String?, status: UploadStatus, capability: OCCapability, - nameCollisionPolicy: NameCollisionPolicy? = null, - onCompleted: (List) -> Unit - ) { - ioScope.launch { - val dao = uploadsStorageManager.uploadDao - val result = if (accountName != null) { - dao.getUploadsByAccountNameAndStatus(accountName, status.value, nameCollisionPolicy?.serialize()) - } else { - dao.getUploadsByStatus(status.value, nameCollisionPolicy?.serialize()) - }.mapNotNull { - it.toOCUpload(capability) - } - onCompleted(result) + nameCollisionPolicy: NameCollisionPolicy? = null + ): List { + val dao = uploadsStorageManager.uploadDao + return if (accountName != null) { + dao.getUploadsByAccountNameAndStatus(accountName, status.value, nameCollisionPolicy?.serialize()) + } else { + dao.getUploadsByStatus(status.value, nameCollisionPolicy?.serialize()) + }.mapNotNull { + it.toOCUpload(capability) } } diff --git a/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java index 69c079d10d9c..d51321efb152 100755 --- a/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java @@ -317,7 +317,7 @@ public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationRe private class UploadFinishReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - throttler.run("update_upload_list", () -> uploadListAdapter.loadUploadItemsFromDb(() -> {})); + throttler.run("update_upload_list", () -> uploadListAdapter.loadUploadItemsFromDb()); } } } diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt index bbd9b886dd19..53dc37dcc962 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt @@ -143,7 +143,7 @@ class UploadListAdapter( UploadListType.COMPLETED -> { uploadsStorageManager.clearSuccessfulUploads() - loadUploadItemsFromDb {} + loadUploadItemsFromDb() } UploadListType.FAILED -> showFailedPopupMenu(holder) @@ -169,7 +169,7 @@ class UploadListAdapter( completedCount++ if (completedCount == items.size) { Log_OC.d(TAG, "refreshing upload items") - loadUploadItemsFromDb {} + loadUploadItemsFromDb() } } } @@ -184,7 +184,7 @@ class UploadListAdapter( R.id.action_upload_list_failed_clear -> { uploadsStorageManager.clearFailedButNotDelayedUploads() clearTempEncryptedFolder() - loadUploadItemsFromDb {} + loadUploadItemsFromDb() } R.id.action_upload_list_failed_retry -> @@ -208,7 +208,7 @@ class UploadListAdapter( when (item.itemId) { R.id.action_upload_list_cancelled_clear -> { uploadsStorageManager.clearCancelledUploadsForCurrentAccount() - loadUploadItemsFromDb {} + loadUploadItemsFromDb() clearTempEncryptedFolder() } @@ -414,7 +414,7 @@ class UploadListAdapter( FileUploadWorker.cancelUpload( item.remotePath, item.accountName - ) { loadUploadItemsFromDb {} } + ) { loadUploadItemsFromDb() } } } } @@ -689,7 +689,7 @@ class UploadListAdapter( fun removeUpload(item: OCUpload?) { uploadsStorageManager.removeUpload(item) cancelOldErrorNotification(item) - loadUploadItemsFromDb {} + loadUploadItemsFromDb() } private fun refreshFolder(view: ItemViewHolder, user: User?, folder: OCFile?, listener: OnRemoteOperationListener) { @@ -723,7 +723,8 @@ class UploadListAdapter( } @SuppressLint("NotifyDataSetChanged") - fun loadUploadItemsFromDb(onCompleted: Runnable) { + @JvmOverloads + fun loadUploadItemsFromDb(onCompleted: Runnable = {}) { val optionalUser = activity.user if (optionalUser.isEmpty) { return @@ -736,14 +737,15 @@ class UploadListAdapter( } val capabilities = optionalCapabilities.get() - uploadListSections.indices.forEach { i -> - val sec = uploadListSections[i] - uploadHelper.getUploadsByStatus( - accountName, - sec.status!!, - capabilities, - sec.collisionPolicy - ) { uploads -> + activity.lifecycleScope.launch(Dispatchers.IO) { + uploadListSections.indices.forEach { i -> + val sec = uploadListSections[i] + val uploads = uploadHelper.getUploadsByStatus( + accountName, + sec.status!!, + capabilities, + sec.collisionPolicy + ) uploads.forEach { it.setDataFixed(uploadHelper) } uploadListSections[i] = sec.withItems(uploads.sortedByUploadOrder()) activity.runOnUiThread { From 1ade5870164d9e94347cf7c6cbfcbb605a17576f Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 27 Mar 2026 16:19:08 +0100 Subject: [PATCH 08/11] refactor(upload-list-adapter)perfomance optimizations Signed-off-by: alperozturk96 --- .../adapter/uploadList/UploadListAdapter.kt | 50 +++++++++++-------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt index 53dc37dcc962..e4efded29bdc 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt @@ -29,6 +29,7 @@ import com.nextcloud.client.network.ConnectivityService import com.nextcloud.utils.extensions.getStatusText import com.nextcloud.utils.extensions.setVisibleIf import com.nextcloud.utils.extensions.sortedByUploadOrder +import com.nextcloud.utils.extensions.toFile import com.owncloud.android.R import com.owncloud.android.databinding.UploadListHeaderBinding import com.owncloud.android.databinding.UploadListItemBinding @@ -486,15 +487,22 @@ class UploadListAdapter( } private fun retryOrShowError(item: OCUpload) { - val file = File(item.localPath) val user = accountManager.getUser(item.accountName) - if (file.exists() && user.isPresent) { - uploadHelper.retryUpload(item, user.get()) - } else { - DisplayUtils.showSnackMessage( - activity, - R.string.local_file_not_found_message - ) + if (user.isEmpty) return + + activity.lifecycleScope.launch(Dispatchers.IO) { + val file = item.localPath.toFile() + + withContext(Dispatchers.Main) { + if (file != null) { + uploadHelper.retryUpload(item, user.get()) + } else { + DisplayUtils.showSnackMessage( + activity, + R.string.local_file_not_found_message + ) + } + } } } @@ -726,20 +734,14 @@ class UploadListAdapter( @JvmOverloads fun loadUploadItemsFromDb(onCompleted: Runnable = {}) { val optionalUser = activity.user - if (optionalUser.isEmpty) { - return - } - val accountName = optionalUser.get().accountName - val optionalCapabilities = activity.capabilities - if (optionalCapabilities.isEmpty) { - return - } + if (optionalUser.isEmpty || optionalCapabilities.isEmpty) return + + val accountName = optionalUser.get().accountName val capabilities = optionalCapabilities.get() activity.lifecycleScope.launch(Dispatchers.IO) { - uploadListSections.indices.forEach { i -> - val sec = uploadListSections[i] + val updatedSections = uploadListSections.map { sec -> val uploads = uploadHelper.getUploadsByStatus( accountName, sec.status!!, @@ -747,11 +749,15 @@ class UploadListAdapter( sec.collisionPolicy ) uploads.forEach { it.setDataFixed(uploadHelper) } - uploadListSections[i] = sec.withItems(uploads.sortedByUploadOrder()) - activity.runOnUiThread { - notifyDataSetChanged() - onCompleted.run() + sec.withItems(uploads.sortedByUploadOrder()) + } + + withContext(Dispatchers.Main) { + for (i in uploadListSections.indices) { + uploadListSections[i] = updatedSections[i] } + notifyDataSetChanged() + onCompleted.run() } } } From 507785acac2744fb8b77b0b790afeeb9133e7cfd Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 27 Mar 2026 16:26:52 +0100 Subject: [PATCH 09/11] refactor(upload-list-adapter)fix tests Signed-off-by: alperozturk96 --- .../owncloud/android/ui/db/OCUploadSortingTest.kt | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/src/test/java/com/owncloud/android/ui/db/OCUploadSortingTest.kt b/app/src/test/java/com/owncloud/android/ui/db/OCUploadSortingTest.kt index f9bcd63c83bf..3e51f19bad3f 100644 --- a/app/src/test/java/com/owncloud/android/ui/db/OCUploadSortingTest.kt +++ b/app/src/test/java/com/owncloud/android/ui/db/OCUploadSortingTest.kt @@ -44,17 +44,23 @@ class OCUploadSortingTest { whenever(failedSameTimeOtherId.fixedUploadStatus).thenReturn(UPLOAD_FAILED) whenever(equalsNotSame.fixedUploadStatus).thenReturn(UPLOAD_FAILED) + whenever(failed.fixedUploadId).thenReturn(UPLOAD_ID) + whenever(failedLater.fixedUploadId).thenReturn(UPLOAD_ID2) + whenever(failedSameTimeOtherId.fixedUploadId).thenReturn(UPLOAD_ID) + whenever(equalsNotSame.fixedUploadId).thenReturn(UPLOAD_ID) + whenever(inProgressNow.isFixedUploadingNow).thenReturn(true) whenever(inProgress.isFixedUploadingNow).thenReturn(false) + whenever(failed.isFixedUploadingNow).thenReturn(false) + whenever(failedLater.isFixedUploadingNow).thenReturn(false) + whenever(failedSameTimeOtherId.isFixedUploadingNow).thenReturn(false) + whenever(equalsNotSame.isFixedUploadingNow).thenReturn(false) + whenever(failed.fixedUploadEndTimeStamp).thenReturn(FIXED_UPLOAD_END_TIMESTAMP) whenever(failedLater.fixedUploadEndTimeStamp).thenReturn(FIXED_UPLOAD_END_TIMESTAMP_LATER) whenever(failedSameTimeOtherId.fixedUploadEndTimeStamp).thenReturn(FIXED_UPLOAD_END_TIMESTAMP) whenever(equalsNotSame.fixedUploadEndTimeStamp).thenReturn(FIXED_UPLOAD_END_TIMESTAMP) - - whenever(failedLater.fixedUploadId).thenReturn(UPLOAD_ID2) - whenever(failedSameTimeOtherId.fixedUploadId).thenReturn(UPLOAD_ID) - whenever(equalsNotSame.fixedUploadId).thenReturn(UPLOAD_ID) } } From 4b60a85d9d0aa429d4315852decda99855db072f Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 27 Mar 2026 16:29:21 +0100 Subject: [PATCH 10/11] refactor(upload-list-adapter)fix comments Signed-off-by: alperozturk96 --- .../java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt index 0a3477242ab0..a22b5a5999cb 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt @@ -339,13 +339,10 @@ class FileUploadHelper { * belonging to that account are retrieved. If [accountName] is `null`, uploads with the * given [status] from **all user accounts** are returned. * - * Once the uploads are fetched, the [onCompleted] callback is invoked with the resulting array. - * * @param accountName The name of the account to filter uploads by. * If `null`, uploads matching the given [status] from all accounts are returned. * @param status The [UploadStatus] to filter uploads by (e.g., `UPLOAD_FAILED`). * @param nameCollisionPolicy The [NameCollisionPolicy] to filter uploads by (e.g., `SKIP`). - * @param onCompleted A callback invoked with the resulting array of [OCUpload] objects. */ suspend fun getUploadsByStatus( accountName: String?, From 0dbbaed665adc2e19158ece5031204ac392d7986 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 27 Mar 2026 16:32:05 +0100 Subject: [PATCH 11/11] refactor(upload-list-adapter)remove nullable fields Signed-off-by: alperozturk96 --- .../owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt | 2 +- .../owncloud/android/ui/adapter/uploadList/UploadListSection.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt index e4efded29bdc..248bb46e4c9a 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt @@ -744,7 +744,7 @@ class UploadListAdapter( val updatedSections = uploadListSections.map { sec -> val uploads = uploadHelper.getUploadsByStatus( accountName, - sec.status!!, + sec.status, capabilities, sec.collisionPolicy ) diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListSection.kt b/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListSection.kt index 43ded6738632..6cb1bcc96995 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListSection.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListSection.kt @@ -15,7 +15,7 @@ import com.owncloud.android.files.services.NameCollisionPolicy data class UploadListSection( val type: UploadListType?, val titleRes: Int, - val status: UploadsStorageManager.UploadStatus?, + val status: UploadsStorageManager.UploadStatus, val collisionPolicy: NameCollisionPolicy?, val items: List ) {