From d6a98618c32176b83682e503c3bb334d9ad2f520 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 13 Oct 2026 10:27:32 +0200 Subject: [PATCH 1/8] Rename .java to .kt Signed-off-by: alperozturk96 --- .../ui/adapter/{OCFileListAdapter.java => OCFileListAdapter.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/src/main/java/com/owncloud/android/ui/adapter/{OCFileListAdapter.java => OCFileListAdapter.kt} (100%) diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.kt similarity index 100% rename from app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java rename to app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.kt From 476a76d6b8957f352ee50116d11aa1b042e242d6 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 13 Oct 2026 10:27:33 +0200 Subject: [PATCH 2/8] convert java to kotlin Signed-off-by: alperozturk96 # Conflicts: # app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.kt --- .../ui/fragment/SharedListFragmentIT.kt | 2 +- .../CommonOCFileListAdapterInterface.kt | 2 +- .../android/ui/adapter/GalleryAdapter.kt | 2 +- .../android/ui/adapter/OCFileListAdapter.kt | 1494 ++++++----------- .../ui/fragment/OCFileListSearchTask.kt | 2 +- 5 files changed, 517 insertions(+), 985 deletions(-) diff --git a/app/src/androidTest/java/com/owncloud/android/ui/fragment/SharedListFragmentIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/fragment/SharedListFragmentIT.kt index 38eb520e45f2..9bff0348fca8 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/fragment/SharedListFragmentIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/fragment/SharedListFragmentIT.kt @@ -171,7 +171,7 @@ internal class SharedListFragmentIT : AbstractIT() { .parseAndSaveShares(listOf(), shares, storageManager, user) } fragment.adapter.run { - prepareForSearchData(storageManager, SearchType.SHARED_FILTER) + prepareForSearchData(SearchType.SHARED_FILTER) updateAdapter(newList, null) } EspressoIdlingResource.decrement() diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/CommonOCFileListAdapterInterface.kt b/app/src/main/java/com/owncloud/android/ui/adapter/CommonOCFileListAdapterInterface.kt index 94962be8a753..e9b1651b9d58 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/CommonOCFileListAdapterInterface.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/CommonOCFileListAdapterInterface.kt @@ -26,7 +26,7 @@ interface CommonOCFileListAdapterInterface { ) fun setHighlightedItem(file: OCFile) - fun setSortOrder(mFile: OCFile, sortOrder: FileSortOrder) + fun setSortOrder(mFile: OCFile?, sortOrder: FileSortOrder) fun addCheckedFile(file: OCFile) fun isCheckedFile(file: OCFile): Boolean fun getCheckedItems(): Set diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/GalleryAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/GalleryAdapter.kt index ee06009657f0..f6ee043f9710 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/GalleryAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/GalleryAdapter.kt @@ -391,7 +391,7 @@ class GalleryAdapter( override fun setHighlightedItem(file: OCFile) = Unit - override fun setSortOrder(mFile: OCFile, sortOrder: FileSortOrder) = Unit + override fun setSortOrder(mFile: OCFile?, sortOrder: FileSortOrder) = Unit fun cleanup() { ocFileListDelegate.cleanup() diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.kt index a0d09f008656..82906cc92942 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.kt @@ -8,1196 +8,728 @@ * SPDX-FileCopyrightText: 2018 Nextcloud * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only */ -package com.owncloud.android.ui.adapter; - -import android.accounts.AccountManager; -import android.annotation.SuppressLint; -import android.app.Activity; -import android.content.res.ColorStateList; -import android.content.res.Resources; -import android.graphics.Color; -import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.os.Looper; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; - -import com.elyeproj.loaderviewlibrary.LoaderImageView; -import com.google.android.material.chip.Chip; -import com.nextcloud.android.common.core.utils.ecosystem.EcosystemApp; -import com.nextcloud.android.common.ui.theme.utils.ColorRole; -import com.nextcloud.client.account.User; -import com.nextcloud.client.database.entity.OfflineOperationEntity; -import com.nextcloud.client.jobs.upload.FileUploadHelper; -import com.nextcloud.client.preferences.AppPreferences; -import com.nextcloud.model.OfflineOperationType; -import com.nextcloud.utils.HumanReadableFormatter; -import com.nextcloud.utils.avatar.AvatarGenerationListener; -import com.nextcloud.utils.avatar.AvatarGenerator; -import com.nextcloud.utils.e2ee.E2EVersionHelper; -import com.nextcloud.utils.extensions.ImageViewExtensionsKt; -import com.nextcloud.utils.extensions.ViewExtensionsKt; -import com.nextcloud.utils.mdm.MDMConfig; -import com.nextcloud.utils.text.DisplayTextFormatter; -import com.nextcloud.utils.thumbnail.ThumbnailGenerator; -import com.nextcloud.utils.view.LocaleDirection; -import com.owncloud.android.MainApp; -import com.owncloud.android.R; -import com.owncloud.android.databinding.GridItemBinding; -import com.owncloud.android.databinding.ListFooterBinding; -import com.owncloud.android.databinding.ListHeaderBinding; -import com.owncloud.android.databinding.ListItemBinding; -import com.owncloud.android.datamodel.FileDataStorageManager; -import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.datamodel.OCFileListAdapterDataProviderImpl; -import com.owncloud.android.datamodel.SyncedFolderProvider; -import com.owncloud.android.datamodel.ThumbnailsCacheManager; -import com.owncloud.android.datamodel.VirtualFolderType; -import com.owncloud.android.lib.common.accounts.AccountUtils; -import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.lib.resources.status.OCCapability; -import com.owncloud.android.lib.resources.tags.Tag; -import com.owncloud.android.ui.activity.ComponentsGetter; -import com.owncloud.android.ui.activity.DrawerActivity; -import com.owncloud.android.ui.activity.FileDisplayActivity; -import com.owncloud.android.ui.adapter.filelist.AvatarShareesProvider; -import com.owncloud.android.ui.adapter.filelist.OCFileListAdapterDataProvider; -import com.owncloud.android.ui.adapter.filelist.OCFileListAdapterHelper; -import com.owncloud.android.ui.adapter.filelist.OCFileListDiffer; -import com.owncloud.android.ui.fragment.OCFileListFragment; -import com.owncloud.android.ui.fragment.SearchType; -import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface; -import com.owncloud.android.ui.preview.PreviewTextFragment; -import com.owncloud.android.utils.EncryptionUtils; -import com.owncloud.android.utils.FileSortOrder; -import com.owncloud.android.utils.FileStorageUtils; -import com.owncloud.android.utils.MimeTypeUtil; -import com.owncloud.android.utils.theme.CapabilityUtils; -import com.owncloud.android.utils.theme.ViewThemeUtils; - -import java.io.File; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Locale; -import java.util.Set; -import java.util.UUID; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import kotlin.Pair; -import kotlin.Unit; -import me.zhanghai.android.fastscroll.PopupTextProvider; +package com.owncloud.android.ui.adapter + +import android.accounts.AccountManager +import android.annotation.SuppressLint +import android.app.Activity +import android.graphics.drawable.Drawable +import android.os.Handler +import android.os.Looper +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import androidx.annotation.VisibleForTesting +import androidx.core.view.isVisible +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.nextcloud.android.common.core.utils.ecosystem.EcosystemApp +import com.nextcloud.android.common.ui.theme.utils.ColorRole +import com.nextcloud.client.account.User +import com.nextcloud.client.jobs.upload.FileUploadHelper +import com.nextcloud.client.preferences.AppPreferences +import com.nextcloud.utils.HumanReadableFormatter +import com.nextcloud.utils.avatar.AvatarGenerationListener +import com.nextcloud.utils.avatar.AvatarGenerator +import com.nextcloud.utils.e2ee.E2EVersionHelper +import com.nextcloud.utils.extensions.setVisibleIf +import com.nextcloud.utils.extensions.stopShimmer +import com.nextcloud.utils.mdm.MDMConfig +import com.nextcloud.utils.thumbnail.ThumbnailGenerator +import com.owncloud.android.MainApp +import com.owncloud.android.R +import com.owncloud.android.databinding.GridItemBinding +import com.owncloud.android.databinding.ListFooterBinding +import com.owncloud.android.databinding.ListHeaderBinding +import com.owncloud.android.databinding.ListHeaderOpenInBinding +import com.owncloud.android.databinding.ListItemBinding +import com.owncloud.android.datamodel.FileDataStorageManager +import com.owncloud.android.datamodel.OCFile +import com.owncloud.android.datamodel.OCFileListAdapterDataProviderImpl +import com.owncloud.android.datamodel.SyncedFolderProvider +import com.owncloud.android.datamodel.ThumbnailsCacheManager +import com.owncloud.android.datamodel.VirtualFolderType +import com.owncloud.android.lib.common.accounts.AccountUtils +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.lib.resources.status.OCCapability +import com.owncloud.android.ui.activity.ComponentsGetter +import com.owncloud.android.ui.activity.DrawerActivity +import com.owncloud.android.ui.activity.FileDisplayActivity +import com.owncloud.android.ui.adapter.filelist.OCFileListAdapterDataProvider +import com.owncloud.android.ui.adapter.filelist.OCFileListAdapterHelper +import com.owncloud.android.ui.adapter.filelist.OCFileListDiffer +import com.owncloud.android.ui.adapter.filelist.OCFileListItemBinder +import com.owncloud.android.ui.fragment.OCFileListFragment +import com.owncloud.android.ui.fragment.SearchType +import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface +import com.owncloud.android.ui.preview.PreviewTextFragment +import com.owncloud.android.utils.EncryptionUtils +import com.owncloud.android.utils.FileSortOrder +import com.owncloud.android.utils.FileSortOrder.SortType +import com.owncloud.android.utils.MimeTypeUtil +import com.owncloud.android.utils.theme.CapabilityUtils +import com.owncloud.android.utils.theme.ViewThemeUtils +import me.zhanghai.android.fastscroll.PopupTextProvider +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import java.util.UUID /** * This Adapter populates a RecyclerView with all files and folders in a Nextcloud instance. */ -@SuppressWarnings("unchecked") -public class OCFileListAdapter extends RecyclerView.Adapter - implements AvatarGenerationListener, - CommonOCFileListAdapterInterface, PopupTextProvider { - - private final String userId; - private final Activity activity; - private final AppPreferences preferences; - private final OCCapability capability; - private final List mFilesAll = new ArrayList<>(); - private final boolean hideItemOptions; - private boolean gridView; - public ArrayList listOfHiddenFiles = new ArrayList<>(); - private FileDataStorageManager mStorageManager; - private OCFileListAdapterDataProvider adapterDataProvider; - private User user; - private final OCFileListFragmentInterface ocFileListFragmentInterface; - private final boolean isRTL; - - private OCFile currentDirectory; - private OCFile listedDirectory; - private static final String TAG = OCFileListAdapter.class.getSimpleName(); - - private static final int VIEW_TYPE_FOOTER = 0; - private static final int VIEW_TYPE_ITEM = 1; - private static final int VIEW_TYPE_IMAGE = 2; - private static final int VIEW_TYPE_HEADER = 3; - - private boolean onlyOnDevice; - private final OCFileListDelegate ocFileListDelegate; - private FileSortOrder sortOrder; - - private final SimpleDateFormat dateFormat = new SimpleDateFormat("MMMM yyyy", Locale.getDefault()); - private final ViewThemeUtils viewThemeUtils; - private SearchType searchType; - - private final long footerId = UUID.randomUUID().getLeastSignificantBits(); - private final long headerId = UUID.randomUUID().getLeastSignificantBits(); - - private final List recommendedFiles = new ArrayList<>(); - private RecommendedFilesAdapter recommendedFilesAdapter; - private final OCFileListAdapterHelper helper = new OCFileListAdapterHelper(); - private final AvatarShareesProvider avatarShareesProvider = new AvatarShareesProvider(); - private final ThumbnailGenerator thumbnailGenerator; - private final AvatarGenerator avatarGenerator; - - private final OCFileListDiffer differ; - - public OCFileListAdapter( - Activity activity, - @NonNull User user, - AppPreferences preferences, - SyncedFolderProvider syncedFolderProvider, - ComponentsGetter transferServiceGetter, - OCFileListFragmentInterface ocFileListFragmentInterface, - boolean argHideItemOptions, - boolean gridView, - final ViewThemeUtils viewThemeUtils, - ThumbnailGenerator thumbnailGenerator, - AvatarGenerator avatarGenerator) { - this.thumbnailGenerator = thumbnailGenerator; - this.avatarGenerator = avatarGenerator; - this.ocFileListFragmentInterface = ocFileListFragmentInterface; - this.activity = activity; - this.preferences = preferences; - differ = new OCFileListDiffer(this, preferences, this::shouldShowHeader); - this.user = user; - hideItemOptions = argHideItemOptions; - this.gridView = gridView; - mStorageManager = transferServiceGetter.getStorageManager(); - this.capability = CapabilityUtils.getCapability(user, activity); - - if (activity instanceof FileDisplayActivity) { - ((FileDisplayActivity) activity).showSortListGroup(true); - } - - if (mStorageManager == null) { - mStorageManager = new FileDataStorageManager(user, activity.getContentResolver()); - } - - adapterDataProvider = new OCFileListAdapterDataProviderImpl(mStorageManager); - - userId = AccountManager - .get(activity) - .getUserData(this.user.toPlatformAccount(), - AccountUtils.Constants.KEY_USER_ID); - this.viewThemeUtils = viewThemeUtils; - ocFileListDelegate = new OCFileListDelegate(FileUploadHelper.Companion.instance(), - activity, - ocFileListFragmentInterface, - user, - mStorageManager, - hideItemOptions, - gridView, - transferServiceGetter, - true, - true, - viewThemeUtils, - syncedFolderProvider); - - setHasStableIds(true); - - // initialize thumbnails cache on background thread - ThumbnailsCacheManager.initDiskCacheAsync(); - isRTL = LocaleDirection.isRtl(); - } - - public boolean isMultiSelect() { - return ocFileListDelegate.isMultiSelect(); - } +@Suppress("LongParameterList", "TooManyFunctions") +class OCFileListAdapter( + private val activity: Activity, + private var user: User, + private val preferences: AppPreferences, + syncedFolderProvider: SyncedFolderProvider?, + transferServiceGetter: ComponentsGetter, + private val ocFileListFragmentInterface: OCFileListFragmentInterface, + private val hideItemOptions: Boolean, + private var gridView: Boolean, + private val viewThemeUtils: ViewThemeUtils, + private val thumbnailGenerator: ThumbnailGenerator, + avatarGenerator: AvatarGenerator +) : RecyclerView.Adapter(), + AvatarGenerationListener, + CommonOCFileListAdapterInterface, + PopupTextProvider { + + val listOfHiddenFiles = ArrayList() + + private val allFiles = mutableListOf() + private val recommendedFiles = mutableListOf() + private var recommendedFilesAdapter: RecommendedFilesAdapter? = null + + private val dateFormat = SimpleDateFormat(POPUP_DATE_FORMAT, Locale.getDefault()) + private val footerId = UUID.randomUUID().leastSignificantBits + private val headerId = UUID.randomUUID().leastSignificantBits + private val helper = OCFileListAdapterHelper() + private val mainHandler = Handler(Looper.getMainLooper()) + private val differ = OCFileListDiffer(this, preferences, ::shouldShowHeader) + + private var currentDirectory: OCFile? = null + private var listedDirectory: OCFile? = null + private var onlyOnDevice = false + private var sortOrder: FileSortOrder? = null + private var searchType: SearchType? = null + + private var storageManager: FileDataStorageManager = + transferServiceGetter.storageManager ?: FileDataStorageManager(user, activity.contentResolver) + private var adapterDataProvider: OCFileListAdapterDataProvider = OCFileListAdapterDataProviderImpl(storageManager) + private val capability: OCCapability = CapabilityUtils.getCapability(user, activity) + private val userId: String? = AccountManager + .get(activity) + .getUserData(user.toPlatformAccount(), AccountUtils.Constants.KEY_USER_ID) + + private val ocFileListDelegate = OCFileListDelegate( + FileUploadHelper.instance(), + activity, + ocFileListFragmentInterface, + user, + storageManager, + hideItemOptions, + gridView, + transferServiceGetter, + showMetadata = true, + showShareAvatar = true, + viewThemeUtils, + syncedFolderProvider + ) + + private val itemBinder = OCFileListItemBinder( + activity, + viewThemeUtils, + ocFileListFragmentInterface, + avatarGenerator, + userId + ) + + val isEmpty: Boolean + get() = differ.latestFiles.isEmpty() + + val isHeaderDisplayed: Boolean + get() = differ.isHeaderDisplayed + + val files: List + get() = differ.files + + init { + (activity as? FileDisplayActivity)?.showSortListGroup(true) + setHasStableIds(true) + ThumbnailsCacheManager.initDiskCacheAsync() + } + + override fun isMultiSelect(): Boolean = ocFileListDelegate.isMultiSelect @SuppressLint("NotifyDataSetChanged") - public void setMultiSelect(boolean bool) { - ocFileListDelegate.setMultiSelect(bool); - notifyDataSetChanged(); + override fun setMultiSelect(isMultiSelect: Boolean) { + ocFileListDelegate.isMultiSelect = isMultiSelect + notifyDataSetChanged() } - public void removeCheckedFile(@NonNull OCFile file) { - ocFileListDelegate.removeCheckedFile(file); - } + override fun removeCheckedFile(file: OCFile) = ocFileListDelegate.removeCheckedFile(file) - @Override - public void selectAll(boolean value) { + override fun selectAll(value: Boolean) { if (value) { - ocFileListDelegate.addToCheckedFiles(getFiles()); + ocFileListDelegate.addToCheckedFiles(files) } else { - clearCheckedItems(); + clearCheckedItems() } } - public int getItemPosition(@NonNull OCFile file) { - int position = getFiles().indexOf(file); - if (position == -1) { - return -1; - } - - return position + differ.headerOffset(); + override fun getItemPosition(file: OCFile): Int { + val position = files.indexOf(file) + return if (position == RecyclerView.NO_POSITION) position else position + differ.headerOffset() } - public void setFavoriteAttributeForItemID(String remotePath, boolean favorite, boolean removeFromList) { - List filesToDelete = new ArrayList<>(); - OCFile changedFile = null; - for (OCFile file : differ.getLatestFiles()) { - if (file.getRemotePath().equals(remotePath)) { - file.setFavorite(favorite); - changedFile = file; - - if (removeFromList) { - filesToDelete.add(file); - } + fun setFavoriteAttributeForItemID(remotePath: String?, favorite: Boolean, removeFromList: Boolean) { + val filesToDelete = mutableListOf() - break; + val changedFile = differ.latestFiles.firstOrNull { it.remotePath == remotePath }?.also { file -> + file.isFavorite = favorite + if (removeFromList) { + filesToDelete.add(file) } } - for (OCFile file : mFilesAll) { - if (file.getRemotePath().equals(remotePath)) { - file.setFavorite(favorite); - - mStorageManager.saveFile(file); - - if (removeFromList) { - filesToDelete.add(file); - } - - break; + allFiles.firstOrNull { it.remotePath == remotePath }?.let { file -> + file.isFavorite = favorite + storageManager.saveFile(file) + if (removeFromList) { + filesToDelete.add(file) } } - if (currentDirectory != null && currentDirectory.getRemotePath().equals(remotePath)) { - currentDirectory.setFavorite(favorite); - mStorageManager.saveFile(currentDirectory); - } - - final OCFile updatedFile = changedFile; - new Handler(Looper.getMainLooper()).post(() -> { - List sortedFiles = differ.sortForCurrentView(new ArrayList<>(differ.getLatestFiles()), - searchType, - currentDirectory); - sortedFiles.removeAll(filesToDelete); - differ.submit(sortedFiles); - - if (updatedFile == null || removeFromList) { - return; - } - - int position = getItemPosition(updatedFile); - if (position != -1) { - notifyItemChanged(position); - } - }); - } - - public void refreshCommentsCount(String fileId) { - for (OCFile file : getFiles()) { - if (file.getRemoteId().equals(fileId)) { - file.setUnreadCommentsCount(0); - break; - } - } + mainHandler.post { + val sortedFiles = differ.sortForCurrentView( + differ.latestFiles.toMutableList(), + searchType, + currentDirectory + ) + sortedFiles.removeAll(filesToDelete) + differ.submit(sortedFiles) - for (OCFile file : mFilesAll) { - if (file.getRemoteId().equals(fileId)) { - file.setUnreadCommentsCount(0); - break; + if (changedFile != null && !removeFromList) { + notifyItemChangedIfPresent(changedFile) } } - - new Handler(Looper.getMainLooper()).post(this::notifyDataSetChanged); } - public void updateFileEncryptionById(String fileId, boolean encrypted) { - if (fileId == null) return; - - mFilesAll.stream() - .filter(f -> fileId.equals(f.getRemoteId())) - .findFirst() - .ifPresent(file -> { - file.setEncrypted(encrypted); - final var isE2EEV2 = E2EVersionHelper.INSTANCE.isV2Plus(capability); - long e2eCounter = EncryptionUtils.E2E_V1_INITIAL_COUNTER; - if (isE2EEV2) { - e2eCounter = EncryptionUtils.E2E_V2_INITIAL_COUNTER; - } - - mStorageManager.updateE2EECounter(file, e2eCounter); - - int position = getItemPosition(file); - if (position != -1) { - notifyItemChanged(position); - } - }); + @SuppressLint("NotifyDataSetChanged") + fun refreshCommentsCount(fileId: String?) { + files.firstOrNull { it.remoteId == fileId }?.unreadCommentsCount = 0 + allFiles.firstOrNull { it.remoteId == fileId }?.unreadCommentsCount = 0 + mainHandler.post { notifyDataSetChanged() } } - @Override - public long getItemId(int position) { - if (isHeaderDisplayed()) { - if (position == 0) { - return headerId; - } - - - // skip header - position--; - } + fun updateFileEncryptionById(fileId: String?, encrypted: Boolean) { + val file = fileId?.let { id -> allFiles.firstOrNull { it.remoteId == id } } ?: return - if (position == getFiles().size()) { - return footerId; - } if (position < getFiles().size()) { - return getFiles().get(position).getFileId(); + file.isEncrypted = encrypted + val e2eCounter = if (E2EVersionHelper.isV2Plus(capability)) { + EncryptionUtils.E2E_V2_INITIAL_COUNTER + } else { + EncryptionUtils.E2E_V1_INITIAL_COUNTER } - - // fallback - return RecyclerView.NO_ID; + storageManager.updateE2EECounter(file, e2eCounter) + notifyItemChangedIfPresent(file) } - @Override - public int getItemCount() { - return getFiles().size() + (isHeaderDisplayed() ? 2 : 1); - } - - @Nullable - public OCFile getItem(int position) { - if (getFiles() == null || getFiles().isEmpty()) { - return null; + override fun getItemId(position: Int): Long { + if (isHeaderDisplayed && position == 0) { + return headerId } - if (position < 0) { - return null; + val files = files + val filePosition = position - differ.headerOffset() + return when { + filePosition == files.size -> footerId + filePosition < files.size -> files[filePosition].fileId + else -> RecyclerView.NO_ID } + } - int newPosition = position; - - if (isHeaderDisplayed()) { - if (position == 0) { - // Header position — no file here - return null; - } - newPosition = position - 1; - } + override fun getItemCount(): Int = files.size + differ.headerOffset() + FOOTER_ITEM_COUNT - if (newPosition >= getFiles().size()) { - return null; - } + fun getItem(position: Int): OCFile? = files.getOrNull(position - differ.headerOffset()) - return getFiles().get(newPosition); + override fun getItemViewType(position: Int): Int = when { + isHeaderDisplayed && position == 0 -> VIEW_TYPE_HEADER + position - differ.headerOffset() == files.size -> VIEW_TYPE_FOOTER + getItem(position)?.let { MimeTypeUtil.isImageOrVideo(it) } == true -> VIEW_TYPE_IMAGE + else -> VIEW_TYPE_ITEM } - @Override - public int getItemViewType(int position) { - if (isHeaderDisplayed() && position == 0) { - return VIEW_TYPE_HEADER; - } + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) + return when { + viewType == VIEW_TYPE_FOOTER -> OCFileListFooterViewHolder( + ListFooterBinding.inflate(inflater, parent, false) + ) - if (isHeaderDisplayed() && position == getFiles().size() + 1 || - (!isHeaderDisplayed() && position == getFiles().size())) { - return VIEW_TYPE_FOOTER; - } + viewType == VIEW_TYPE_HEADER -> OCFileListHeaderViewHolder( + ListHeaderBinding.inflate(inflater, parent, false) + ) - OCFile item = getItem(position); - if (item == null) { - return VIEW_TYPE_ITEM; - } + gridView -> OCFileListGridItemViewHolder(GridItemBinding.inflate(inflater, parent, false)) - if (MimeTypeUtil.isImageOrVideo(item)) { - return VIEW_TYPE_IMAGE; - } else { - return VIEW_TYPE_ITEM; + else -> OCFileListItemViewHolder(ListItemBinding.inflate(inflater, parent, false)) } } - public boolean isEmpty() { - return differ.getLatestFiles().isEmpty(); + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is OCFileListFooterViewHolder -> bindFooter(holder) + is OCFileListHeaderViewHolder -> bindHeader(holder) + else -> bindItem(holder as ListViewHolder, position) + } } - @NonNull - @Override - public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - switch (viewType) { - case VIEW_TYPE_FOOTER -> { - return new OCFileListFooterViewHolder( - ListFooterBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false) - ); - } - case VIEW_TYPE_HEADER -> { - ListHeaderBinding binding = ListHeaderBinding.inflate( - LayoutInflater.from(parent.getContext()), - parent, - false); - - return new OCFileListHeaderViewHolder(binding); - } - default -> { - if (gridView) { - return new OCFileListGridItemViewHolder( - GridItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false) - ); - } else { - return new OCFileListItemViewHolder( - ListItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false) - ); - } - } + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int, payloads: MutableList) { + val iconId = payloads.firstOrNull() as? Int + if (iconId != null && OCFileListDiffer.REBIND_PAYLOAD !in payloads && holder is ListViewHolder) { + holder.localFileIndicator.setImageResource(iconId) + holder.localFileIndicator.visibility = View.VISIBLE + return } + super.onBindViewHolder(holder, position, payloads) } - @Override - public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { - if (holder instanceof OCFileListFooterViewHolder footerViewHolder) { - footerViewHolder.getFooterText().setText(getFooterText()); - viewThemeUtils.platform.colorCircularProgressBar(footerViewHolder.getLoadingProgressBar(), ColorRole.ON_SURFACE_VARIANT); - footerViewHolder.getLoadingProgressBar().setVisibility( - ocFileListFragmentInterface.isLoading() ? View.VISIBLE : View.GONE); - } else if (holder instanceof OCFileListHeaderViewHolder headerViewHolder) { - ListHeaderBinding headerBinding = headerViewHolder.getBinding(); - headerViewHolder.getHeaderView().setOnClickListener(v -> ocFileListFragmentInterface.onHeaderClicked()); - - String text = currentDirectory.getRichWorkspace(); - PreviewTextFragment.setText(headerViewHolder.getHeaderText(), text, null, activity, true, true, viewThemeUtils); - - // hide header text if empty (server returns NBSP) - ViewExtensionsKt.setVisibleIf(headerViewHolder.getHeaderText(), text != null && !text.isBlank() && !" ".equals(text)); - - ViewExtensionsKt.setVisibleIf(headerBinding.recommendedFilesRecyclerView, shouldShowRecommendedFiles()); - ViewExtensionsKt.setVisibleIf(headerBinding.recommendedFilesTitle, shouldShowRecommendedFiles()); - ViewExtensionsKt.setVisibleIf(headerBinding.allFilesTitle, shouldShowRecommendedFiles()); - - if (shouldShowRecommendedFiles()) { - final var recommendedFilesRecyclerView = headerBinding.recommendedFilesRecyclerView; - - final LinearLayoutManager layoutManager = new LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false); - recommendedFilesRecyclerView.setLayoutManager(layoutManager); - - recommendedFilesAdapter = new RecommendedFilesAdapter(this, recommendedFiles); - recommendedFilesRecyclerView.setAdapter(recommendedFilesAdapter); - } - - ViewExtensionsKt.setVisibleIf(headerBinding.openIn.getRoot(), shouldShowOpenInNotes()); - - if (shouldShowOpenInNotes()) { - final var listHeaderOpenInBinding = headerBinding.openIn; - - viewThemeUtils.files.themeFilledCardView(listHeaderOpenInBinding.infoCard); + fun bindRecommendedFilesHolder(holder: OCFileListRecommendedItemViewHolder, file: OCFile) = + bindListViewHolder(holder, file) - listHeaderOpenInBinding.infoText.setText(String.format(activity.getString(R.string.folder_best_viewed_in), - activity.getString(R.string.ecosystem_apps_notes))); - - listHeaderOpenInBinding.openInButton.setText(String.format(activity.getString(R.string.open_in_app), - activity.getString(R.string.ecosystem_apps_display_notes))); - - if (activity instanceof DrawerActivity drawerActivity) { - final var ecosystemManager = drawerActivity.getEcosystemManager(); - if (ecosystemManager != null) { - listHeaderOpenInBinding.openInButton.setOnClickListener(v -> ecosystemManager.openApp(EcosystemApp.NOTES, user.getAccountName())); - } - } - } - - } else { - ListViewHolder viewHolder = (ListViewHolder) holder; - OCFile file = getItem(position); - - if (file == null) { - Log_OC.e(this, "Cannot bind on view holder on a null file"); - return; - } - - bindHolder(holder, viewHolder, file); - } + private fun bindFooter(holder: OCFileListFooterViewHolder) { + holder.footerText.text = footerText() + viewThemeUtils.platform.colorCircularProgressBar(holder.loadingProgressBar, ColorRole.ON_SURFACE_VARIANT) + holder.loadingProgressBar.setVisibleIf(ocFileListFragmentInterface.isLoading) } - public void bindRecommendedFilesHolder(OCFileListRecommendedItemViewHolder holder, @NonNull OCFile file) { - bindHolder(holder, holder, file); + private fun bindHeader(holder: OCFileListHeaderViewHolder) { + holder.headerView.setOnClickListener { ocFileListFragmentInterface.onHeaderClicked() } + bindRichWorkspace(holder) + bindRecommendedFiles(holder.binding) + bindOpenInNotes(holder.binding.openIn) } - private void bindHolder(@NonNull RecyclerView.ViewHolder holder, ListViewHolder viewHolder, OCFile file) { - ocFileListDelegate.bindViewHolder(viewHolder, file, currentDirectory, searchType, thumbnailGenerator); - - if (holder instanceof ListItemViewHolder itemViewHolder) { - bindListItemViewHolder(itemViewHolder, file); - } + private fun bindRichWorkspace(holder: OCFileListHeaderViewHolder) { + val text = currentDirectory?.richWorkspace + PreviewTextFragment.setText( + holder.headerText, + text, + null, + activity, + ignoreMimetype = true, + preview = true, + viewThemeUtils + ) + holder.headerText.setVisibleIf(isRichWorkspaceTextVisible(text)) + } - if (holder instanceof ListGridItemViewHolder gridItemViewHolder) { - setFilenameAndExtension(gridItemViewHolder, file); - } + private fun isRichWorkspaceTextVisible(text: String?): Boolean = + text != null && text != EMPTY_RICH_WORKSPACE && text.any { !Character.isWhitespace(it) } - updateLivePhotoIndicators(viewHolder, file); + private fun bindRecommendedFiles(binding: ListHeaderBinding) { + val showRecommendedFiles = shouldShowRecommendedFiles() + binding.recommendedFilesRecyclerView.setVisibleIf(showRecommendedFiles) + binding.recommendedFilesTitle.setVisibleIf(showRecommendedFiles) + binding.allFilesTitle.setVisibleIf(showRecommendedFiles) - if (!MDMConfig.INSTANCE.sharingSupport(activity)) { - viewHolder.getShared().setVisibility(View.GONE); + if (!showRecommendedFiles) { + return } - setVisibilityOfMoreOption(viewHolder); - - final var fileFeatureLayout = viewHolder.getFileFeaturesLayout(); - if (fileFeatureLayout != null) { - ViewExtensionsKt.setVisibleIf(fileFeatureLayout, viewHolder.getHasVisibleFeatureIndicators()); + binding.recommendedFilesRecyclerView.run { + layoutManager = LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false) + adapter = RecommendedFilesAdapter(this@OCFileListAdapter, recommendedFiles) + .also { recommendedFilesAdapter = it } } } - private boolean shouldShowRecommendedFiles() { - return !recommendedFiles.isEmpty() && currentDirectory.isRootDirectory(); - } + private fun bindOpenInNotes(binding: ListHeaderOpenInBinding) { + val showOpenInNotes = shouldShowOpenInNotes() + binding.root.setVisibleIf(showOpenInNotes) - private boolean shouldShowOpenInNotes() { - if (!preferences.isShowEcosystemApps()) { - return false; + if (!showOpenInNotes) { + return } - String notesFolderPath = capability.getNotesFolderPath(); - String currentPath = currentDirectory.getDecryptedRemotePath(); - return notesFolderPath != null && currentPath != null && currentPath.startsWith(notesFolderPath); - } - - private void updateLivePhotoIndicators(ListViewHolder holder, OCFile file) { - boolean isLivePhoto = file.getLinkedFileIdForLivePhoto() != null; - if (holder instanceof OCFileListItemViewHolder) { - holder.getLivePhotoIndicator().setVisibility(isLivePhoto ? (View.VISIBLE) : (View.GONE)); - holder.getLivePhotoIndicatorSeparator().setVisibility(isLivePhoto ? (View.VISIBLE) : (View.GONE)); - } else if (holder instanceof OCFileListViewHolder) { - holder.getGridLivePhotoIndicator().setVisibility(isLivePhoto ? (View.VISIBLE) : (View.GONE)); - } - } - - private void setFilenameAndExtension(ListGridItemViewHolder holder, OCFile file) { - final String filename = mStorageManager.getFilenameConsideringOfflineOperation(file); - final var pair = FileStorageUtils.getFilenameAndExtension(filename, file.isFolder(), isRTL); - - if (holder instanceof OCFileListGridItemViewHolder gridItemViewHolder) { - handleGridMode(filename, gridItemViewHolder, pair, file); - } else { - handleListMode(holder, filename, pair); - } + viewThemeUtils.files.themeFilledCardView(binding.infoCard) + binding.infoText.text = activity.getString(R.string.folder_best_viewed_in) + .format(activity.getString(R.string.ecosystem_apps_notes)) + binding.openInButton.text = activity.getString(R.string.open_in_app) + .format(activity.getString(R.string.ecosystem_apps_display_notes)) - final var playerProgressIndicator = holder.getPlayerProgressIndicator(); - if (playerProgressIndicator != null) { - playerProgressIndicator.setFile(file); - } + val ecosystemManager = (activity as? DrawerActivity)?.ecosystemManager ?: return + binding.openInButton.setOnClickListener { ecosystemManager.openApp(EcosystemApp.NOTES, user.accountName) } } - private void handleGridMode(String filename, OCFileListGridItemViewHolder holder, Pair filenamePair, OCFile file) { - boolean containsBidiControlCharacters = FileStorageUtils.containsBidiControlCharacters(filename); - ViewExtensionsKt.setVisibleIf(holder.getFileName(),!containsBidiControlCharacters); - ViewExtensionsKt.setVisibleIf(holder.getBidiFilenameContainer(), containsBidiControlCharacters); - final var extension = holder.getExtension(); - - if (containsBidiControlCharacters) { - holder.getBidiFilename().setText(filenamePair.getFirst()); - if (extension != null) { - extension.setText(filenamePair.getSecond()); - } - holder.getBinding().more.setVisibility(View.GONE); - holder.getBinding().bidiMore.setOnClickListener(v -> ocFileListFragmentInterface.onOverflowIconClicked(file, v)); - } else { - holder.getFileName().setText(filename); - if (extension != null) { - extension.setVisibility(View.GONE); - } + private fun bindItem(holder: ListViewHolder, position: Int) { + val file = getItem(position) + if (file == null) { + Log_OC.e(this, "Cannot bind on view holder on a null file") + return } + bindListViewHolder(holder, file) } - private void handleListMode(ListGridItemViewHolder holder, - String filename, - Pair filenamePair) { - final boolean containsBidiControlCharacters = FileStorageUtils.containsBidiControlCharacters(filename); - ViewExtensionsKt.setVisibleIf(holder.getFileName(), !containsBidiControlCharacters); - ViewExtensionsKt.setVisibleIf(holder.getBidiFilenameContainer(), containsBidiControlCharacters); + private fun bindListViewHolder(holder: ListViewHolder, file: OCFile) { + ocFileListDelegate.bindViewHolder(holder, file, currentDirectory, searchType, thumbnailGenerator) - if (!containsBidiControlCharacters) { - holder.getFileName().setText(filename); - return; + if (holder is ListItemViewHolder) { + itemBinder.bindDetails(holder, file, user, storageManager, shouldShowItemOptions(), onlyOnDevice) } - holder.getBidiFilename().setText(filenamePair.getFirst()); - final var extension = holder.getExtension(); - if (extension != null) { - extension.setText(filenamePair.getSecond()); + if (holder is ListGridItemViewHolder) { + itemBinder.bindFilename(holder, file, storageManager.getFilenameConsideringOfflineOperation(file)) } - } - private void bindSharedAvatars(ListItemViewHolder holder, OCFile file) { - final var sharedAvatars = holder.getSharedAvatars(); + itemBinder.bindLivePhotoIndicator(holder, file) - if (!(file.isSharedWithMe() || file.isSharedWithSharee()) || isMultiSelect() || gridView || hideItemOptions) { - sharedAvatars.setBoundFileId(null); - sharedAvatars.setVisibility(View.GONE); - if (sharedAvatars.getChildCount() > 0) { - sharedAvatars.removeAllViews(); - } - return; + if (!MDMConfig.sharingSupport(activity)) { + holder.shared.visibility = View.GONE } - final long fileId = file.getFileId(); - sharedAvatars.setBoundFileId(fileId); - sharedAvatars.setOnClickListener(view -> ocFileListFragmentInterface.onShareIconClick(file)); - - sharedAvatars.setAvatars(user, avatarShareesProvider.get(file, userId), viewThemeUtils, avatarGenerator); + bindMoreOptionVisibility(holder) + holder.fileFeaturesLayout?.setVisibleIf(holder.hasVisibleFeatureIndicators) } - private void bindListItemViewHolder(ListItemViewHolder holder, OCFile file) { - bindSharedAvatars(holder, file); - - // tags - if (file.getTags().isEmpty()) { - holder.getTagsGroup().setVisibility(View.GONE); - holder.getFileDetailGroup().setVisibility(View.VISIBLE); - } else { - holder.getTagsGroup().setVisibility(View.VISIBLE); - holder.getFileDetailGroup().setVisibility(View.GONE); - viewThemeUtils.material.themeChipSuggestion(holder.getFirstTag()); - holder.getFirstTag().setVisibility(View.VISIBLE); - holder.getSecondTag().setVisibility(View.GONE); - holder.getTagMore().setVisibility(View.GONE); - - applyChipVisuals(holder.getFirstTag(), file.getTags().get(0)); - - if (file.getTags().size() > 1) { - holder.getSecondTag().setVisibility(View.VISIBLE); - applyChipVisuals(holder.getSecondTag(), file.getTags().get(1)); - } - - if (file.getTags().size() > 2) { - viewThemeUtils.material.themeChipSuggestion(holder.getTagMore()); - holder.getTagMore().setVisibility(View.VISIBLE); - holder.getTagMore().setText(String.format(activity.getString(R.string.tags_more), - (file.getTags().size() - 2))); - } - } + private fun shouldShowItemOptions(): Boolean = !(isMultiSelect() || gridView || hideItemOptions) - // npe fix: looks like file without local storage path somehow get here - final String storagePath = file.getStoragePath(); - if (onlyOnDevice && storagePath != null) { - File localFile = new File(storagePath); - long localSize; - if (localFile.isDirectory()) { - localSize = FileStorageUtils.getFolderSize(localFile); - } else { - localSize = localFile.length(); - } + private fun bindMoreOptionVisibility(holder: ListViewHolder) { + val showMoreOptions = !isMultiSelect() && !OCFileListFragment.isMultipleFileSelectedForCopyOrMove - prepareFileSize(holder, file, localSize); + if (holder is ListItemViewHolder) { + holder.overflowMenu.setVisibleIf(showMoreOptions) } else { - final long fileLength = file.getFileLength(); - if (fileLength >= 0) { - prepareFileSize(holder, file, fileLength); - } else { - holder.getFileSize().setVisibility(View.GONE); - holder.getFileSizeSeparator().setVisibility(View.GONE); - } + holder.more.setVisibleIf(showMoreOptions) } + } - final long modificationTimestamp = file.getModificationTimestamp(); - if (modificationTimestamp > 0) { - holder.getLastModification().setText( - DisplayTextFormatter.formatRelativeTimestamp(activity, modificationTimestamp)); - holder.getLastModification().setVisibility(View.VISIBLE); - } else if (file.getFirstShareTimestamp() > 0) { - holder.getLastModification().setText( - DisplayTextFormatter.formatRelativeTimestamp(activity, file.getFirstShareTimestamp()) - ); - holder.getLastModification().setVisibility(View.VISIBLE); - } else { - holder.getLastModification().setVisibility(View.GONE); - } + private fun shouldShowRecommendedFiles(): Boolean = + recommendedFiles.isNotEmpty() && currentDirectory?.isRootDirectory == true - if (isMultiSelect() || gridView || hideItemOptions) { - holder.getOverflowMenu().setVisibility(View.GONE); - } else { - holder.getOverflowMenu().setVisibility(View.VISIBLE); - holder.getOverflowMenu().setOnClickListener(view -> ocFileListFragmentInterface - .onOverflowIconClicked(file, view)); + private fun shouldShowOpenInNotes(): Boolean { + if (!preferences.isShowEcosystemApps) { + return false } - if (file.isLocked()) { - holder.getOverflowMenu().setImageResource(R.drawable.ic_locked_dots_small); - } else { - holder.getOverflowMenu().setImageResource(R.drawable.ic_dots_vertical); - } + val notesFolderPath = capability.notesFolderPath + val currentPath = currentDirectory?.decryptedRemotePath + return notesFolderPath != null && currentPath?.startsWith(notesFolderPath) == true } - private void setVisibilityOfMoreOption(Object holder) { - boolean showMoreOptions = (!isMultiSelect() && !OCFileListFragment.isMultipleFileSelectedForCopyOrMove); - - if (holder instanceof ListItemViewHolder itemViewHolder) { - ViewExtensionsKt.setVisibleIf(itemViewHolder.getOverflowMenu(), showMoreOptions); - } else if (holder instanceof ListViewHolder viewHolder) { - ViewExtensionsKt.setVisibleIf(viewHolder.getMore(), showMoreOptions); - } - } + private fun visibleRecommendedFilesAdapterFor(file: OCFile): RecommendedFilesAdapter? = + recommendedFilesAdapter?.takeIf { shouldShowRecommendedFiles() && file.isRecommendedFile } @SuppressLint("NotifyDataSetChanged") - public void updateRecommendedFiles(@NonNull List value) { - recommendedFiles.clear(); - recommendedFiles.addAll(value); - differ.syncHeaderState(); - notifyDataSetChanged(); + fun updateRecommendedFiles(value: List) { + recommendedFiles.clear() + recommendedFiles.addAll(value) + differ.syncHeaderState() + notifyDataSetChanged() } - private void applyChipVisuals(Chip chip, Tag tag) { - viewThemeUtils.material.themeChipSuggestion(chip); - chip.setText(tag.getName()); - String tagColor = tag.getColor(); - if (TextUtils.isEmpty(tagColor)) { - return; + override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder) { + val thumbnailShimmer = (holder as? ListViewHolder)?.shimmerThumbnail ?: return + if (thumbnailShimmer.isVisible) { + thumbnailShimmer.setImageResource(R.drawable.background) + thumbnailShimmer.resetLoader() } - - try { - int color = Color.parseColor(tagColor); - chip.setChipStrokeColor(ColorStateList.valueOf(color)); - chip.setTextColor(color); - } catch (IllegalArgumentException e) { - Log_OC.d(TAG, "Exception applyChipVisuals: " + e); - } - } - - private void prepareFileSize(ListItemViewHolder holder, OCFile file, long size) { - holder.getFileSize().setVisibility(View.VISIBLE); - String fileSizeText = getFileSizeText(file, size); - holder.getFileSize().setText(fileSizeText); } - private String getFileSizeText(OCFile file, long size) { - if (!file.isOfflineOperation()) { - return HumanReadableFormatter.formatBytes(size); - } - - OfflineOperationEntity entity = mStorageManager.getOfflineEntityFromOCFile(file); - boolean isRemoveOperation = (entity != null && entity.getType() instanceof OfflineOperationType.RemoveFile); - if (isRemoveOperation) { - return activity.getString(R.string.oc_file_list_adapter_offline_operation_remove_description_text); + override fun onViewRecycled(holder: RecyclerView.ViewHolder) { + super.onViewRecycled(holder) + if (holder is ListViewHolder) { + holder.thumbnail.stopShimmer(holder.shimmerThumbnail) } - - return activity.getString(R.string.oc_file_list_adapter_offline_operation_description_text); } - @Override - public void onViewAttachedToWindow(@NonNull RecyclerView.ViewHolder holder) { - if (holder instanceof ListViewHolder) { - LoaderImageView thumbnailShimmer = ((ListViewHolder) holder).getShimmerThumbnail(); - if (thumbnailShimmer.getVisibility() == View.VISIBLE) { - thumbnailShimmer.setImageResource(R.drawable.background); - thumbnailShimmer.resetLoader(); - } - } - } + private fun footerText(): String { + val showHiddenFiles = preferences.isShowHiddenFilesEnabled + val foldersCount = files.count { it.isFolder } + val filesCount = files.count { !it.isFolder && (!it.isHidden || showHiddenFiles) } + val resources = activity.resources - private String getFooterText() { - int filesCount = 0; - int foldersCount = 0; - int count = getFiles().size(); - OCFile file; - final boolean showHiddenFiles = preferences.isShowHiddenFilesEnabled(); - for (int i = 0; i < count; i++) { - file = getFiles().get(i); - if (file.isFolder()) { - foldersCount++; - } else { - if (!file.isHidden() || showHiddenFiles) { - filesCount++; - } + return listOfNotNull( + filesCount.takeIf { it > 0 }?.let { + resources.getQuantityString(R.plurals.file_list__footer__file, it, it) + }, + foldersCount.takeIf { it > 0 }?.let { + resources.getQuantityString(R.plurals.file_list__footer__folder, it, it) } - } - - return generateFooterText(filesCount, foldersCount); + ).joinToString(FOOTER_TEXT_SEPARATOR) } - private String generateFooterText(int filesCount, int foldersCount) { - String output; - Resources resources = activity.getResources(); - - if (filesCount + foldersCount <= 0) { - output = ""; - } else if (foldersCount <= 0) { - output = resources.getQuantityString(R.plurals.file_list__footer__file, filesCount, filesCount); - } else if (filesCount <= 0) { - output = resources.getQuantityString(R.plurals.file_list__footer__folder, foldersCount, foldersCount); - } else { - output = resources.getQuantityString(R.plurals.file_list__footer__file, filesCount, filesCount) + ", " + - resources.getQuantityString(R.plurals.file_list__footer__folder, foldersCount, foldersCount); - } - - return output; - } - - public boolean isHeaderDisplayed() { - return differ.isHeaderDisplayed(); - } - - public boolean shouldShowHeader() { - if (currentDirectory == null) { - return false; - } - - if (MainApp.isOnlyOnDevice()) { - return false; - } - - if (shouldShowRecommendedFiles()) { - return true; - } - - if (shouldShowOpenInNotes()) { - return true; - } - - if (currentDirectory.getRichWorkspace() == null) { - return false; + fun shouldShowHeader(): Boolean { + val directory = currentDirectory + if (directory == null || MainApp.isOnlyOnDevice()) { + return false } - return !TextUtils.isEmpty(currentDirectory.getRichWorkspace().trim()); + return shouldShowRecommendedFiles() || + shouldShowOpenInNotes() || + directory.richWorkspace?.any { it > ' ' } == true } /** * Change the adapted directory for a new one * - * @param directory New folder to adapt. Can be NULL, meaning "no content to adapt". - * @param updatedStorageManager Optional updated storage manager; used to replace - * @param limitToMimeType show only files of this mimeType + * @param directory New folder to adapt. + * @param storageManager Replaces the current storage manager when it differs from it. + * @param mLimitToMimeType show only files of this mimeType */ - @SuppressLint("NotifyDataSetChanged") - public void swapDirectory( - @NonNull User account, - @NonNull OCFile directory, - @NonNull FileDataStorageManager updatedStorageManager, - boolean onlyOnDevice, - @NonNull String limitToMimeType) { - - this.onlyOnDevice = onlyOnDevice; - - if (!updatedStorageManager.equals(mStorageManager)) { - mStorageManager = updatedStorageManager; - adapterDataProvider = new OCFileListAdapterDataProviderImpl(mStorageManager); - ocFileListDelegate.setShowShareAvatar(true); - this.user = account; - } + override fun swapDirectory( + user: User, + directory: OCFile, + storageManager: FileDataStorageManager, + onlyOnDevice: Boolean, + mLimitToMimeType: String + ) { + this.onlyOnDevice = onlyOnDevice - if (mStorageManager == null) { - updateAdapter(new ArrayList<>(), null); - return; + if (storageManager != this.storageManager) { + this.storageManager = storageManager + adapterDataProvider = OCFileListAdapterDataProviderImpl(storageManager) + ocFileListDelegate.setShowShareAvatar(true) + this.user = user } - if (userId == null) { - return; - } + val accountUserId = userId ?: return - helper.prepareFileList(directory, - adapterDataProvider, - onlyOnDevice, - limitToMimeType, - preferences, - userId, - (newList, fileSortOrder) -> - { - updateAdapter((List) newList, directory); - return Unit.INSTANCE; - }); + helper.prepareFileList( + directory, + adapterDataProvider, + onlyOnDevice, + mLimitToMimeType, + preferences, + accountUserId + ) { newList, _ -> + updateAdapter(newList, directory) + } } - public void updateAdapter(List newFiles, OCFile directory) { - Log_OC.d(TAG, "updating the adapter"); + fun updateAdapter(newFiles: List, directory: OCFile?) { + Log_OC.d(TAG, "updating the adapter") - mFilesAll.clear(); - mFilesAll.addAll(newFiles); + allFiles.clear() + allFiles.addAll(newFiles) - activity.runOnUiThread(() -> { - boolean isSameDirectoryRefresh = directory != null && directory.equals(listedDirectory); - listedDirectory = directory; + activity.runOnUiThread { + val isSameDirectoryRefresh = directory != null && directory == listedDirectory + listedDirectory = directory if (directory != null) { - currentDirectory = directory; + currentDirectory = directory } - differ.updateHeader(); + differ.updateHeader() - List files = new ArrayList<>(newFiles); + val files = newFiles.toList() if (isSameDirectoryRefresh) { - differ.submitAndRebind(files); + differ.submitAndRebind(files) } else { - differ.replace(files); - } - }); - } - - public void prepareForSearchData(FileDataStorageManager storageManager, SearchType searchType) { - this.searchType = searchType; - initStorageManagerShowShareAvatar(storageManager); - clearSearchData(searchType); - } - - private void initStorageManagerShowShareAvatar(FileDataStorageManager storageManager) { - if (mStorageManager == null) { - mStorageManager = (storageManager != null) - ? storageManager - : new FileDataStorageManager(user, activity.getContentResolver()); - - if (storageManager != null) { - adapterDataProvider = new OCFileListAdapterDataProviderImpl(mStorageManager); - ocFileListDelegate.setShowShareAvatar(true); + differ.replace(files) } } } - private void clearSearchData(SearchType searchType) { - preferences.setPhotoSearchTimestamp(0); + fun prepareForSearchData(searchType: SearchType) { + this.searchType = searchType + preferences.setPhotoSearchTimestamp(0) - VirtualFolderType type = switch (searchType) { - case FAVORITE_SEARCH -> VirtualFolderType.FAVORITE; - case GALLERY_SEARCH -> VirtualFolderType.GALLERY; - default -> VirtualFolderType.NONE; - }; + val type = when (searchType) { + SearchType.FAVORITE_SEARCH -> VirtualFolderType.FAVORITE + SearchType.GALLERY_SEARCH -> VirtualFolderType.GALLERY + else -> VirtualFolderType.NONE + } if (type != VirtualFolderType.GALLERY) { - mStorageManager.deleteVirtuals(type); + storageManager.deleteVirtuals(type) } } - public void setSortOrder(FileSortOrder newSortOrder) { - sortOrder = newSortOrder; + fun setSortOrder(newSortOrder: FileSortOrder?) { + sortOrder = newSortOrder } - public void setSortOrder(@Nullable OCFile folder, @NonNull FileSortOrder sortOrder) { + override fun setSortOrder(mFile: OCFile?, sortOrder: FileSortOrder) { if (searchType == SearchType.FAVORITE_SEARCH) { - preferences.setSortOrder(FileSortOrder.Type.favoritesListView, sortOrder); + preferences.setSortOrder(FileSortOrder.Type.favoritesListView, sortOrder) } else { - preferences.setSortOrder(folder, sortOrder); + preferences.setSortOrder(mFile, sortOrder) } - boolean foldersBeforeFiles = preferences.isSortFoldersBeforeFiles(); - boolean favoritesFirst = preferences.isSortFavoritesFirst(); - List files = new ArrayList<>(differ.getLatestFiles()); - differ.replace(sortOrder.sortCloudFiles(files, foldersBeforeFiles, favoritesFirst)); + val sortedFiles = sortOrder.sortCloudFiles( + differ.latestFiles.toMutableList(), + preferences.isSortFoldersBeforeFiles, + preferences.isSortFavoritesFirst + ) + differ.replace(sortedFiles) - this.sortOrder = sortOrder; + this.sortOrder = sortOrder } - @NonNull - public Set getCheckedItems() { - return ocFileListDelegate.getCheckedItems(); - } + override fun getCheckedItems(): Set = ocFileListDelegate.checkedItems - public void setCheckedItem(Set files) { - ocFileListDelegate.setCheckedItem(files); - } - - public void clearCheckedItems() { - ocFileListDelegate.clearCheckedItems(); - } + fun setCheckedItem(files: Set?) = ocFileListDelegate.setCheckedItem(files) - public void setFiles(List files) { - differ.submit(new ArrayList<>(files)); - } + override fun clearCheckedItems() = ocFileListDelegate.clearCheckedItems() - public List getFiles() { - return differ.getFiles(); - } + fun setFiles(files: List) = differ.submit(files.toList()) - public void replaceFile(@NonNull OCFile file) { - List newFiles = new ArrayList<>(differ.getLatestFiles()); - int index = newFiles.indexOf(file); - if (index == -1) { - Log_OC.d(TAG, "File cannot be found in adapter's files"); - return; + fun replaceFile(file: OCFile) { + val index = differ.latestFiles.indexOf(file) + if (index == RecyclerView.NO_POSITION) { + Log_OC.d(TAG, "File cannot be found in adapter's files") + return } - newFiles.set(index, file); - differ.submit(newFiles); - notifyItemChanged(file); + differ.submit(differ.latestFiles.toMutableList().apply { set(index, file) }) + notifyItemChanged(file) } - @Nullable - public OCFile getFileByRemoteId(@Nullable String fileId) { - return mFilesAll.stream() - .filter(f -> java.util.Objects.equals(fileId, f.getRemoteId())) - .findFirst() - .orElse(null); - } + fun getFileByRemoteId(fileId: String?): OCFile? = allFiles.firstOrNull { it.remoteId == fileId } - public void insertFile(@Nullable OCFile file) { - if (file == null) return; - - if (mFilesAll.contains(file)) return; - - mFilesAll.add(file); + fun insertFile(file: OCFile?) { + if (file == null || file in allFiles) { + return + } - List newFiles = new ArrayList<>(differ.getLatestFiles()); - newFiles.add(file); - differ.submit(newFiles); + allFiles.add(file) + differ.submit(differ.latestFiles + file) } - @Override - public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) { - super.onAttachedToRecyclerView(recyclerView); - differ.attach(recyclerView); + override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { + super.onAttachedToRecyclerView(recyclerView) + differ.attach(recyclerView) } - @Override - public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) { - super.onDetachedFromRecyclerView(recyclerView); - differ.detach(); + override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { + super.onDetachedFromRecyclerView(recyclerView) + differ.detach() } - @Override - public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) { - super.onViewRecycled(holder); - if (holder instanceof ListViewHolder listViewHolder) { - LoaderImageView thumbnailShimmer = listViewHolder.getShimmerThumbnail(); - ImageViewExtensionsKt.stopShimmer(listViewHolder.getThumbnail(), thumbnailShimmer); - } + override fun avatarGenerated(avatarDrawable: Drawable?, callContext: Any?) { + (callContext as ImageView).setImageDrawable(avatarDrawable) } - @Override - public void avatarGenerated(Drawable avatarDrawable, Object callContext) { - ((ImageView) callContext).setImageDrawable(avatarDrawable); - } + override fun shouldCallGeneratedCallback(tag: String?, callContext: Any?): Boolean = + (callContext as ImageView).tag == tag - @Override - public boolean shouldCallGeneratedCallback(String tag, Object callContext) { - return ((ImageView) callContext).getTag().equals(tag); - } - - public boolean isCheckedFile(@NonNull OCFile file) { - return ocFileListDelegate.isCheckedFile(file); - } + override fun isCheckedFile(file: OCFile): Boolean = ocFileListDelegate.isCheckedFile(file) - public void addCheckedFile(@NonNull OCFile file) { - ocFileListDelegate.addCheckedFile(file); - } + override fun addCheckedFile(file: OCFile) = ocFileListDelegate.addCheckedFile(file) - public void setHighlightedItem(@NonNull OCFile file) { - ocFileListDelegate.setHighlightedItem(file); - } + override fun setHighlightedItem(file: OCFile) = ocFileListDelegate.setHighlightedItem(file) - public void cancelAllPendingTasks() { - thumbnailGenerator.getFileThumbnailGenerator().cancelPendingTasks(); - } + override fun cancelAllPendingTasks() = thumbnailGenerator.fileThumbnailGenerator.cancelPendingTasks() - public void setGridView(boolean bool) { - gridView = bool; + fun setGridView(bool: Boolean) { + gridView = bool } - public void setShowMetadata(boolean bool) { - ocFileListDelegate.setMultiSelect(bool); + fun setShowMetadata(bool: Boolean) { + ocFileListDelegate.isMultiSelect = bool } - @NonNull - @Override - public String getPopupText(@NonNull View view, int position) { - OCFile file = getItem(position); - + override fun getPopupText(view: View, position: Int): String { + val file = getItem(position) + val sortOrder = sortOrder if (file == null || sortOrder == null) { - return ""; + return "" } - switch (sortOrder.getType()) { - case ALPHABET: - return String.valueOf(file.getFileName().charAt(0)).toUpperCase(Locale.getDefault()); - case DATE: - long milliseconds = file.getModificationTimestamp(); - Date date = new Date(milliseconds); - return dateFormat.format(date); - case SIZE: - return HumanReadableFormatter.formatBytes(file.getFileLength()); - default: - Log_OC.d(TAG, "getPopupText: Unsupported sort order: " + sortOrder.getType()); - return ""; + return when (sortOrder.getType()) { + SortType.ALPHABET -> file.fileName.take(1).uppercase(Locale.getDefault()) + SortType.DATE -> dateFormat.format(Date(file.modificationTimestamp)) + SortType.SIZE -> HumanReadableFormatter.formatBytes(file.fileLength) } } @VisibleForTesting - public void setShowShareAvatar(boolean bool) { - ocFileListDelegate.setShowShareAvatar(bool); - } + fun setShowShareAvatar(bool: Boolean) = ocFileListDelegate.setShowShareAvatar(bool) - @Override - public int getFilesCount() { - return getFiles().size(); - } + override fun getFilesCount(): Int = files.size - @Override - public void notifyItemChanged(@NonNull OCFile file) { - if (shouldShowRecommendedFiles() && recommendedFilesAdapter != null && file.isRecommendedFile()) { - final int position = recommendedFilesAdapter.getItemPosition(file); - recommendedFilesAdapter.notifyItemChanged(position); - return; + override fun notifyItemChanged(file: OCFile) { + val recommendedAdapter = visibleRecommendedFilesAdapterFor(file) + if (recommendedAdapter != null) { + recommendedAdapter.notifyItemChanged(recommendedAdapter.getItemPosition(file)) + } else { + notifyItemChangedIfPresent(file) } + } - int position = getItemPosition(file); - if (position != -1) { - notifyItemChanged(position); + private fun notifyItemChangedIfPresent(file: OCFile, payload: Any? = null) { + val position = getItemPosition(file) + if (position != RecyclerView.NO_POSITION) { + notifyItemChanged(position, payload) } } @VisibleForTesting - public void setCurrentDirectory(OCFile folder) { - currentDirectory = folder; - differ.syncHeaderState(); + fun setCurrentDirectory(folder: OCFile?) { + currentDirectory = folder + differ.syncHeaderState() } - @Override - public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List payloads) { - if (!payloads.isEmpty() && payloads.get(0) instanceof Integer iconId && - !payloads.contains(OCFileListDiffer.REBIND_PAYLOAD) && - holder instanceof ListViewHolder listViewHolder) { - listViewHolder.getLocalFileIndicator().setImageResource(iconId); - listViewHolder.getLocalFileIndicator().setVisibility(View.VISIBLE); - // skip full rebind - return; - } - super.onBindViewHolder(holder, position, payloads); - } - - public void updateFileIndicator(int iconId, OCFile file) { - if (file == null) return; - - int position = getItemPosition(file); - if (position != -1) { - notifyItemChanged(position, iconId); - } + fun updateFileIndicator(iconId: Int, file: OCFile?) { + file?.let { notifyItemChangedIfPresent(it, iconId) } } - public void cleanup() { - ocFileListDelegate.cleanup(); - helper.cleanup(); + fun cleanup() { + ocFileListDelegate.cleanup() + helper.cleanup() } - public void removeAllFiles() { - mFilesAll.clear(); - differ.submit(new ArrayList<>()); + fun removeAllFiles() { + allFiles.clear() + differ.submit(emptyList()) } - public void removeFile(@NonNull OCFile file) { - mFilesAll.remove(file); - - List newFiles = new ArrayList<>(differ.getLatestFiles()); - newFiles.remove(file); - differ.submit(newFiles); + fun removeFile(file: OCFile) { + allFiles.remove(file) + differ.submit(differ.latestFiles - file) } - public void updateFile(@NonNull OCFile updatedFile) { - int allIndex = helper.indexOfSameRemoteFile(mFilesAll, updatedFile); - if (allIndex != -1) { - mFilesAll.set(allIndex, updatedFile); + fun updateFile(updatedFile: OCFile) { + val allIndex = helper.indexOfSameRemoteFile(allFiles, updatedFile) + if (allIndex != RecyclerView.NO_POSITION) { + allFiles[allIndex] = updatedFile } - List newFiles = new ArrayList<>(differ.getLatestFiles()); - int oldIndex = helper.indexOfSameRemoteFile(newFiles, updatedFile); - if (oldIndex == -1) { - return; + val newFiles = differ.latestFiles.toMutableList() + val oldIndex = helper.indexOfSameRemoteFile(newFiles, updatedFile) + if (oldIndex == RecyclerView.NO_POSITION) { + return } - newFiles.remove(oldIndex); - newFiles.add(updatedFile); + newFiles.removeAt(oldIndex) + newFiles.add(updatedFile) + differ.submit(differ.sortForCurrentView(newFiles, searchType, currentDirectory)) - differ.submit(differ.sortForCurrentView(newFiles, searchType, currentDirectory)); - - if (shouldShowRecommendedFiles() && recommendedFilesAdapter != null && updatedFile.isRecommendedFile()) { - int pos = recommendedFilesAdapter.getItemPosition(updatedFile); - if (pos != -1) recommendedFilesAdapter.notifyItemChanged(pos); + visibleRecommendedFilesAdapterFor(updatedFile)?.let { adapter -> + val position = adapter.getItemPosition(updatedFile) + if (position != RecyclerView.NO_POSITION) { + adapter.notifyItemChanged(position) + } } } + companion object { + private val TAG = OCFileListAdapter::class.java.simpleName + + private const val VIEW_TYPE_FOOTER = 0 + private const val VIEW_TYPE_ITEM = 1 + private const val VIEW_TYPE_IMAGE = 2 + private const val VIEW_TYPE_HEADER = 3 + + private const val FOOTER_ITEM_COUNT = 1 + private const val FOOTER_TEXT_SEPARATOR = ", " + private const val EMPTY_RICH_WORKSPACE = " " + private const val POPUP_DATE_FORMAT = "MMMM yyyy" + } } diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchTask.kt b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchTask.kt index 7bd6762d7a20..bf8104ff4899 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchTask.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchTask.kt @@ -85,7 +85,7 @@ class OCFileListSearchTask( return@launch } - fragment.adapter.prepareForSearchData(storageManager, fragment.currentSearchType) + fragment.adapter.prepareForSearchData(fragment.currentSearchType) val remoteFiles = fetchAndSortRemoteFiles(searchType, cachedFiles, resultData, fragment) updateAdapterData(fragment, remoteFiles) } From bba721124e1b676a4899d8de67498d4db1d8accc Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 13 Oct 2026 10:27:41 +0200 Subject: [PATCH 3/8] extract item binder Signed-off-by: alperozturk96 --- .../adapter/filelist/OCFileListItemBinder.kt | 250 ++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListItemBinder.kt diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListItemBinder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListItemBinder.kt new file mode 100644 index 000000000000..7b7a2d655d1a --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListItemBinder.kt @@ -0,0 +1,250 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.owncloud.android.ui.adapter.filelist + +import android.app.Activity +import android.content.res.ColorStateList +import android.graphics.Color +import android.view.View +import com.google.android.material.chip.Chip +import com.nextcloud.client.account.User +import com.nextcloud.model.OfflineOperationType +import com.nextcloud.utils.HumanReadableFormatter +import com.nextcloud.utils.avatar.AvatarGenerator +import com.nextcloud.utils.extensions.setVisibleIf +import com.nextcloud.utils.text.DisplayTextFormatter +import com.nextcloud.utils.view.LocaleDirection +import com.owncloud.android.R +import com.owncloud.android.datamodel.FileDataStorageManager +import com.owncloud.android.datamodel.OCFile +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.lib.resources.tags.Tag +import com.owncloud.android.ui.adapter.ListGridItemViewHolder +import com.owncloud.android.ui.adapter.ListItemViewHolder +import com.owncloud.android.ui.adapter.ListViewHolder +import com.owncloud.android.ui.adapter.OCFileListGridItemViewHolder +import com.owncloud.android.ui.adapter.OCFileListItemViewHolder +import com.owncloud.android.ui.adapter.OCFileListViewHolder +import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface +import com.owncloud.android.utils.FileStorageUtils +import com.owncloud.android.utils.theme.ViewThemeUtils +import java.io.File + +internal class OCFileListItemBinder( + private val activity: Activity, + private val viewThemeUtils: ViewThemeUtils, + private val fragmentInterface: OCFileListFragmentInterface, + private val avatarGenerator: AvatarGenerator, + private val userId: String? +) { + private val avatarShareesProvider = AvatarShareesProvider() + private val isRTL = LocaleDirection.isRtl + + fun bindDetails( + holder: ListItemViewHolder, + file: OCFile, + user: User, + storageManager: FileDataStorageManager, + showItemOptions: Boolean, + onlyOnDevice: Boolean + ) { + bindSharedAvatars(holder, file, user, showItemOptions) + bindTags(holder, file.tags) + bindFileSize(holder, file, storageManager, onlyOnDevice) + bindLastModification(holder, file) + bindOverflowMenu(holder, file, showItemOptions) + } + + fun bindFilename(holder: ListGridItemViewHolder, file: OCFile, filename: String?) { + val hasBidiControlCharacters = FileStorageUtils.containsBidiControlCharacters(filename) + holder.fileName.setVisibleIf(!hasBidiControlCharacters) + holder.bidiFilenameContainer.setVisibleIf(hasBidiControlCharacters) + + if (hasBidiControlCharacters) { + bindBidiFilename(holder, file, filename) + } else { + bindPlainFilename(holder, filename) + } + + holder.playerProgressIndicator?.setFile(file) + } + + fun bindLivePhotoIndicator(holder: ListViewHolder, file: OCFile) { + val isLivePhoto = file.linkedFileIdForLivePhoto != null + + when (holder) { + is OCFileListItemViewHolder -> { + holder.livePhotoIndicator.setVisibleIf(isLivePhoto) + holder.livePhotoIndicatorSeparator.setVisibleIf(isLivePhoto) + } + + is OCFileListViewHolder -> holder.gridLivePhotoIndicator.setVisibleIf(isLivePhoto) + } + } + + private fun bindPlainFilename(holder: ListGridItemViewHolder, filename: String?) { + holder.fileName.text = filename + + if (holder is OCFileListGridItemViewHolder) { + holder.extension?.visibility = View.GONE + } + } + + private fun bindBidiFilename(holder: ListGridItemViewHolder, file: OCFile, filename: String?) { + val (name, extension) = FileStorageUtils.getFilenameAndExtension(filename, file.isFolder, isRTL) + holder.bidiFilename.text = name + holder.extension?.text = extension + + if (holder is OCFileListGridItemViewHolder) { + holder.binding.more.visibility = View.GONE + holder.binding.bidiMore.setOnClickListener { fragmentInterface.onOverflowIconClicked(file, it) } + } + } + + private fun bindSharedAvatars(holder: ListItemViewHolder, file: OCFile, user: User, showItemOptions: Boolean) { + val sharedAvatars = holder.sharedAvatars + val isShared = file.isSharedWithMe || file.isSharedWithSharee + + if (!isShared || !showItemOptions) { + sharedAvatars.boundFileId = null + sharedAvatars.visibility = View.GONE + if (sharedAvatars.childCount > 0) { + sharedAvatars.removeAllViews() + } + return + } + + sharedAvatars.boundFileId = file.fileId + sharedAvatars.setOnClickListener { fragmentInterface.onShareIconClick(file) } + sharedAvatars.setAvatars(user, avatarShareesProvider.get(file, userId), viewThemeUtils, avatarGenerator) + } + + private fun bindTags(holder: ListItemViewHolder, tags: List) { + val hasTags = tags.isNotEmpty() + holder.tagsGroup.setVisibleIf(hasTags) + holder.fileDetailGroup.setVisibleIf(!hasTags) + + if (!hasTags) { + return + } + + viewThemeUtils.material.themeChipSuggestion(holder.firstTag) + holder.firstTag.visibility = View.VISIBLE + holder.secondTag.visibility = View.GONE + holder.tagMore.visibility = View.GONE + + applyChipVisuals(holder.firstTag, tags[0]) + + if (tags.size > 1) { + holder.secondTag.visibility = View.VISIBLE + applyChipVisuals(holder.secondTag, tags[1]) + } + + val hiddenTagCount = tags.size - MAX_VISIBLE_TAGS + if (hiddenTagCount > 0) { + viewThemeUtils.material.themeChipSuggestion(holder.tagMore) + holder.tagMore.visibility = View.VISIBLE + holder.tagMore.text = activity.getString(R.string.tags_more).format(hiddenTagCount) + } + } + + private fun applyChipVisuals(chip: Chip, tag: Tag) { + viewThemeUtils.material.themeChipSuggestion(chip) + chip.text = tag.name + + val tagColor = tag.color + if (tagColor.isNullOrEmpty()) { + return + } + + try { + val color = Color.parseColor(tagColor) + chip.chipStrokeColor = ColorStateList.valueOf(color) + chip.setTextColor(color) + } catch (e: IllegalArgumentException) { + Log_OC.d(TAG, "Exception applyChipVisuals: $e") + } + } + + private fun bindFileSize( + holder: ListItemViewHolder, + file: OCFile, + storageManager: FileDataStorageManager, + onlyOnDevice: Boolean + ) { + val localPath = file.storagePath?.takeIf { onlyOnDevice } + + when { + localPath != null -> showFileSize(holder, file, localSizeOf(File(localPath)), storageManager) + + file.fileLength >= 0 -> showFileSize(holder, file, file.fileLength, storageManager) + + else -> { + holder.fileSize.visibility = View.GONE + holder.fileSizeSeparator.visibility = View.GONE + } + } + } + + private fun localSizeOf(localFile: File): Long = if (localFile.isDirectory) { + FileStorageUtils.getFolderSize(localFile) + } else { + localFile.length() + } + + private fun showFileSize( + holder: ListItemViewHolder, + file: OCFile, + size: Long, + storageManager: FileDataStorageManager + ) { + holder.fileSize.visibility = View.VISIBLE + holder.fileSize.text = fileSizeText(file, size, storageManager) + } + + private fun fileSizeText(file: OCFile, size: Long, storageManager: FileDataStorageManager): String { + if (!file.isOfflineOperation) { + return HumanReadableFormatter.formatBytes(size) + } + + val isRemoveOperation = storageManager.getOfflineEntityFromOCFile(file)?.type is OfflineOperationType.RemoveFile + val descriptionRes = if (isRemoveOperation) { + R.string.oc_file_list_adapter_offline_operation_remove_description_text + } else { + R.string.oc_file_list_adapter_offline_operation_description_text + } + return activity.getString(descriptionRes) + } + + private fun bindLastModification(holder: ListItemViewHolder, file: OCFile) { + val timestamp = file.modificationTimestamp.takeIf { it > 0 } + ?: file.firstShareTimestamp.takeIf { it > 0 } + + if (timestamp != null) { + holder.lastModification.text = DisplayTextFormatter.formatRelativeTimestamp(activity, timestamp) + } + holder.lastModification.setVisibleIf(timestamp != null) + } + + private fun bindOverflowMenu(holder: ListItemViewHolder, file: OCFile, showItemOptions: Boolean) { + val overflowMenu = holder.overflowMenu + overflowMenu.setVisibleIf(showItemOptions) + + if (showItemOptions) { + overflowMenu.setOnClickListener { fragmentInterface.onOverflowIconClicked(file, it) } + } + + val iconRes = if (file.isLocked) R.drawable.ic_locked_dots_small else R.drawable.ic_dots_vertical + overflowMenu.setImageResource(iconRes) + } + + companion object { + private val TAG = OCFileListItemBinder::class.java.simpleName + + private const val MAX_VISIBLE_TAGS = 2 + } +} From 83636b97afee32ded250221878bf24ee3f000f2a Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 13 Oct 2026 10:36:00 +0200 Subject: [PATCH 4/8] simplify adapter Signed-off-by: alperozturk96 --- .../android/ui/adapter/OCFileListAdapter.kt | 672 ++++++++---------- .../filelist/OCFileListFooterBinder.kt | 54 ++ .../filelist/OCFileListHeaderBinder.kt | 120 ++++ .../adapter/filelist/OCFileListItemBinder.kt | 4 + .../ui/adapter/filelist/OCFileListViewType.kt | 18 + .../ui/fragment/OCFileListFragment.java | 4 +- .../ui/fragment/OCFileListSearchTask.kt | 2 +- 7 files changed, 494 insertions(+), 380 deletions(-) create mode 100644 app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListFooterBinder.kt create mode 100644 app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListHeaderBinder.kt create mode 100644 app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListViewType.kt diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.kt index 82906cc92942..baed5b64e7b8 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.kt @@ -22,16 +22,14 @@ import android.view.ViewGroup import android.widget.ImageView import androidx.annotation.VisibleForTesting import androidx.core.view.isVisible -import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import com.nextcloud.android.common.core.utils.ecosystem.EcosystemApp -import com.nextcloud.android.common.ui.theme.utils.ColorRole import com.nextcloud.client.account.User import com.nextcloud.client.jobs.upload.FileUploadHelper import com.nextcloud.client.preferences.AppPreferences import com.nextcloud.utils.HumanReadableFormatter import com.nextcloud.utils.avatar.AvatarGenerationListener import com.nextcloud.utils.avatar.AvatarGenerator +import com.nextcloud.utils.date.DateFormatPattern import com.nextcloud.utils.e2ee.E2EVersionHelper import com.nextcloud.utils.extensions.setVisibleIf import com.nextcloud.utils.extensions.stopShimmer @@ -42,7 +40,6 @@ import com.owncloud.android.R import com.owncloud.android.databinding.GridItemBinding import com.owncloud.android.databinding.ListFooterBinding import com.owncloud.android.databinding.ListHeaderBinding -import com.owncloud.android.databinding.ListHeaderOpenInBinding import com.owncloud.android.databinding.ListItemBinding import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.datamodel.OCFile @@ -54,16 +51,17 @@ import com.owncloud.android.lib.common.accounts.AccountUtils import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.lib.resources.status.OCCapability import com.owncloud.android.ui.activity.ComponentsGetter -import com.owncloud.android.ui.activity.DrawerActivity import com.owncloud.android.ui.activity.FileDisplayActivity import com.owncloud.android.ui.adapter.filelist.OCFileListAdapterDataProvider import com.owncloud.android.ui.adapter.filelist.OCFileListAdapterHelper import com.owncloud.android.ui.adapter.filelist.OCFileListDiffer +import com.owncloud.android.ui.adapter.filelist.OCFileListFooterBinder +import com.owncloud.android.ui.adapter.filelist.OCFileListHeaderBinder import com.owncloud.android.ui.adapter.filelist.OCFileListItemBinder +import com.owncloud.android.ui.adapter.filelist.OCFileListViewType import com.owncloud.android.ui.fragment.OCFileListFragment import com.owncloud.android.ui.fragment.SearchType import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface -import com.owncloud.android.ui.preview.PreviewTextFragment import com.owncloud.android.utils.EncryptionUtils import com.owncloud.android.utils.FileSortOrder import com.owncloud.android.utils.FileSortOrder.SortType @@ -76,9 +74,6 @@ import java.util.Date import java.util.Locale import java.util.UUID -/** - * This Adapter populates a RecyclerView with all files and folders in a Nextcloud instance. - */ @Suppress("LongParameterList", "TooManyFunctions") class OCFileListAdapter( private val activity: Activity, @@ -86,10 +81,10 @@ class OCFileListAdapter( private val preferences: AppPreferences, syncedFolderProvider: SyncedFolderProvider?, transferServiceGetter: ComponentsGetter, - private val ocFileListFragmentInterface: OCFileListFragmentInterface, + ocFileListFragmentInterface: OCFileListFragmentInterface, private val hideItemOptions: Boolean, private var gridView: Boolean, - private val viewThemeUtils: ViewThemeUtils, + viewThemeUtils: ViewThemeUtils, private val thumbnailGenerator: ThumbnailGenerator, avatarGenerator: AvatarGenerator ) : RecyclerView.Adapter(), @@ -103,7 +98,7 @@ class OCFileListAdapter( private val recommendedFiles = mutableListOf() private var recommendedFilesAdapter: RecommendedFilesAdapter? = null - private val dateFormat = SimpleDateFormat(POPUP_DATE_FORMAT, Locale.getDefault()) + private val popupDateFormat = SimpleDateFormat(DateFormatPattern.FullMonthWithYear.pattern, Locale.getDefault()) private val footerId = UUID.randomUUID().leastSignificantBits private val headerId = UUID.randomUUID().leastSignificantBits private val helper = OCFileListAdapterHelper() @@ -113,7 +108,7 @@ class OCFileListAdapter( private var currentDirectory: OCFile? = null private var listedDirectory: OCFile? = null private var onlyOnDevice = false - private var sortOrder: FileSortOrder? = null + private var displayedSortOrder: FileSortOrder? = null private var searchType: SearchType? = null private var storageManager: FileDataStorageManager = @@ -139,6 +134,21 @@ class OCFileListAdapter( syncedFolderProvider ) + private val headerBinder = OCFileListHeaderBinder( + activity, + viewThemeUtils, + ocFileListFragmentInterface, + preferences, + capability + ) { user } + + private val footerBinder = OCFileListFooterBinder( + activity, + viewThemeUtils, + preferences, + ocFileListFragmentInterface + ) + private val itemBinder = OCFileListItemBinder( activity, viewThemeUtils, @@ -162,81 +172,8 @@ class OCFileListAdapter( ThumbnailsCacheManager.initDiskCacheAsync() } - override fun isMultiSelect(): Boolean = ocFileListDelegate.isMultiSelect - - @SuppressLint("NotifyDataSetChanged") - override fun setMultiSelect(isMultiSelect: Boolean) { - ocFileListDelegate.isMultiSelect = isMultiSelect - notifyDataSetChanged() - } - - override fun removeCheckedFile(file: OCFile) = ocFileListDelegate.removeCheckedFile(file) - - override fun selectAll(value: Boolean) { - if (value) { - ocFileListDelegate.addToCheckedFiles(files) - } else { - clearCheckedItems() - } - } - - override fun getItemPosition(file: OCFile): Int { - val position = files.indexOf(file) - return if (position == RecyclerView.NO_POSITION) position else position + differ.headerOffset() - } - - fun setFavoriteAttributeForItemID(remotePath: String?, favorite: Boolean, removeFromList: Boolean) { - val filesToDelete = mutableListOf() - - val changedFile = differ.latestFiles.firstOrNull { it.remotePath == remotePath }?.also { file -> - file.isFavorite = favorite - if (removeFromList) { - filesToDelete.add(file) - } - } - - allFiles.firstOrNull { it.remotePath == remotePath }?.let { file -> - file.isFavorite = favorite - storageManager.saveFile(file) - if (removeFromList) { - filesToDelete.add(file) - } - } - - mainHandler.post { - val sortedFiles = differ.sortForCurrentView( - differ.latestFiles.toMutableList(), - searchType, - currentDirectory - ) - sortedFiles.removeAll(filesToDelete) - differ.submit(sortedFiles) - - if (changedFile != null && !removeFromList) { - notifyItemChangedIfPresent(changedFile) - } - } - } - - @SuppressLint("NotifyDataSetChanged") - fun refreshCommentsCount(fileId: String?) { - files.firstOrNull { it.remoteId == fileId }?.unreadCommentsCount = 0 - allFiles.firstOrNull { it.remoteId == fileId }?.unreadCommentsCount = 0 - mainHandler.post { notifyDataSetChanged() } - } - - fun updateFileEncryptionById(fileId: String?, encrypted: Boolean) { - val file = fileId?.let { id -> allFiles.firstOrNull { it.remoteId == id } } ?: return - - file.isEncrypted = encrypted - val e2eCounter = if (E2EVersionHelper.isV2Plus(capability)) { - EncryptionUtils.E2E_V2_INITIAL_COUNTER - } else { - EncryptionUtils.E2E_V1_INITIAL_COUNTER - } - storageManager.updateE2EECounter(file, e2eCounter) - notifyItemChangedIfPresent(file) - } + // region Overridden methods + override fun getItemCount(): Int = files.size + differ.headerOffset() + FOOTER_ITEM_COUNT override fun getItemId(position: Int): Long { if (isHeaderDisplayed && position == 0) { @@ -252,39 +189,28 @@ class OCFileListAdapter( } } - override fun getItemCount(): Int = files.size + differ.headerOffset() + FOOTER_ITEM_COUNT - - fun getItem(position: Int): OCFile? = files.getOrNull(position - differ.headerOffset()) - - override fun getItemViewType(position: Int): Int = when { - isHeaderDisplayed && position == 0 -> VIEW_TYPE_HEADER - position - differ.headerOffset() == files.size -> VIEW_TYPE_FOOTER - getItem(position)?.let { MimeTypeUtil.isImageOrVideo(it) } == true -> VIEW_TYPE_IMAGE - else -> VIEW_TYPE_ITEM - } + override fun getItemViewType(position: Int): Int = viewTypeAt(position).ordinal override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { val inflater = LayoutInflater.from(parent.context) - return when { - viewType == VIEW_TYPE_FOOTER -> OCFileListFooterViewHolder( - ListFooterBinding.inflate(inflater, parent, false) - ) + return when (OCFileListViewType.fromOrdinal(viewType)) { + OCFileListViewType.FOOTER -> OCFileListFooterViewHolder(ListFooterBinding.inflate(inflater, parent, false)) - viewType == VIEW_TYPE_HEADER -> OCFileListHeaderViewHolder( - ListHeaderBinding.inflate(inflater, parent, false) - ) + OCFileListViewType.HEADER -> OCFileListHeaderViewHolder(ListHeaderBinding.inflate(inflater, parent, false)) - gridView -> OCFileListGridItemViewHolder(GridItemBinding.inflate(inflater, parent, false)) - - else -> OCFileListItemViewHolder(ListItemBinding.inflate(inflater, parent, false)) + OCFileListViewType.ITEM, OCFileListViewType.IMAGE -> if (gridView) { + OCFileListGridItemViewHolder(GridItemBinding.inflate(inflater, parent, false)) + } else { + OCFileListItemViewHolder(ListItemBinding.inflate(inflater, parent, false)) + } } } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when (holder) { - is OCFileListFooterViewHolder -> bindFooter(holder) + is OCFileListFooterViewHolder -> footerBinder.bind(holder, files) is OCFileListHeaderViewHolder -> bindHeader(holder) - else -> bindItem(holder as ListViewHolder, position) + else -> bindFileAt(holder as ListViewHolder, position) } } @@ -298,170 +224,150 @@ class OCFileListAdapter( super.onBindViewHolder(holder, position, payloads) } - fun bindRecommendedFilesHolder(holder: OCFileListRecommendedItemViewHolder, file: OCFile) = - bindListViewHolder(holder, file) + override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder) { + val thumbnailShimmer = (holder as? ListViewHolder)?.shimmerThumbnail ?: return + if (thumbnailShimmer.isVisible) { + thumbnailShimmer.setImageResource(R.drawable.background) + thumbnailShimmer.resetLoader() + } + } - private fun bindFooter(holder: OCFileListFooterViewHolder) { - holder.footerText.text = footerText() - viewThemeUtils.platform.colorCircularProgressBar(holder.loadingProgressBar, ColorRole.ON_SURFACE_VARIANT) - holder.loadingProgressBar.setVisibleIf(ocFileListFragmentInterface.isLoading) + override fun onViewRecycled(holder: RecyclerView.ViewHolder) { + super.onViewRecycled(holder) + if (holder is ListViewHolder) { + holder.thumbnail.stopShimmer(holder.shimmerThumbnail) + } } - private fun bindHeader(holder: OCFileListHeaderViewHolder) { - holder.headerView.setOnClickListener { ocFileListFragmentInterface.onHeaderClicked() } - bindRichWorkspace(holder) - bindRecommendedFiles(holder.binding) - bindOpenInNotes(holder.binding.openIn) - } - - private fun bindRichWorkspace(holder: OCFileListHeaderViewHolder) { - val text = currentDirectory?.richWorkspace - PreviewTextFragment.setText( - holder.headerText, - text, - null, - activity, - ignoreMimetype = true, - preview = true, - viewThemeUtils - ) - holder.headerText.setVisibleIf(isRichWorkspaceTextVisible(text)) + override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { + super.onAttachedToRecyclerView(recyclerView) + differ.attach(recyclerView) + } + + override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { + super.onDetachedFromRecyclerView(recyclerView) + differ.detach() } - private fun isRichWorkspaceTextVisible(text: String?): Boolean = - text != null && text != EMPTY_RICH_WORKSPACE && text.any { !Character.isWhitespace(it) } + override fun isMultiSelect(): Boolean = ocFileListDelegate.isMultiSelect - private fun bindRecommendedFiles(binding: ListHeaderBinding) { - val showRecommendedFiles = shouldShowRecommendedFiles() - binding.recommendedFilesRecyclerView.setVisibleIf(showRecommendedFiles) - binding.recommendedFilesTitle.setVisibleIf(showRecommendedFiles) - binding.allFilesTitle.setVisibleIf(showRecommendedFiles) + @SuppressLint("NotifyDataSetChanged") + override fun setMultiSelect(isMultiSelect: Boolean) { + ocFileListDelegate.isMultiSelect = isMultiSelect + notifyDataSetChanged() + } - if (!showRecommendedFiles) { - return - } + override fun cancelAllPendingTasks() = thumbnailGenerator.fileThumbnailGenerator.cancelPendingTasks() - binding.recommendedFilesRecyclerView.run { - layoutManager = LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false) - adapter = RecommendedFilesAdapter(this@OCFileListAdapter, recommendedFiles) - .also { recommendedFilesAdapter = it } - } + override fun getItemPosition(file: OCFile): Int { + val position = files.indexOf(file) + return if (position == RecyclerView.NO_POSITION) position else position + differ.headerOffset() } - private fun bindOpenInNotes(binding: ListHeaderOpenInBinding) { - val showOpenInNotes = shouldShowOpenInNotes() - binding.root.setVisibleIf(showOpenInNotes) + override fun swapDirectory( + user: User, + directory: OCFile, + storageManager: FileDataStorageManager, + onlyOnDevice: Boolean, + mLimitToMimeType: String + ) { + this.onlyOnDevice = onlyOnDevice - if (!showOpenInNotes) { - return + if (storageManager != this.storageManager) { + this.storageManager = storageManager + adapterDataProvider = OCFileListAdapterDataProviderImpl(storageManager) + ocFileListDelegate.setShowShareAvatar(true) + this.user = user } - viewThemeUtils.files.themeFilledCardView(binding.infoCard) - binding.infoText.text = activity.getString(R.string.folder_best_viewed_in) - .format(activity.getString(R.string.ecosystem_apps_notes)) - binding.openInButton.text = activity.getString(R.string.open_in_app) - .format(activity.getString(R.string.ecosystem_apps_display_notes)) - - val ecosystemManager = (activity as? DrawerActivity)?.ecosystemManager ?: return - binding.openInButton.setOnClickListener { ecosystemManager.openApp(EcosystemApp.NOTES, user.accountName) } - } + val accountUserId = userId ?: return - private fun bindItem(holder: ListViewHolder, position: Int) { - val file = getItem(position) - if (file == null) { - Log_OC.e(this, "Cannot bind on view holder on a null file") - return + helper.prepareFileList( + directory, + adapterDataProvider, + onlyOnDevice, + mLimitToMimeType, + preferences, + accountUserId + ) { newList, _ -> + updateAdapter(newList, directory) } - bindListViewHolder(holder, file) } - private fun bindListViewHolder(holder: ListViewHolder, file: OCFile) { - ocFileListDelegate.bindViewHolder(holder, file, currentDirectory, searchType, thumbnailGenerator) + override fun setHighlightedItem(file: OCFile) = ocFileListDelegate.setHighlightedItem(file) - if (holder is ListItemViewHolder) { - itemBinder.bindDetails(holder, file, user, storageManager, shouldShowItemOptions(), onlyOnDevice) + override fun setSortOrder(mFile: OCFile?, sortOrder: FileSortOrder) { + if (searchType == SearchType.FAVORITE_SEARCH) { + preferences.setSortOrder(FileSortOrder.Type.favoritesListView, sortOrder) + } else { + preferences.setSortOrder(mFile, sortOrder) } - if (holder is ListGridItemViewHolder) { - itemBinder.bindFilename(holder, file, storageManager.getFilenameConsideringOfflineOperation(file)) - } + val sortedFiles = sortOrder.sortCloudFiles( + differ.latestFiles.toMutableList(), + preferences.isSortFoldersBeforeFiles, + preferences.isSortFavoritesFirst + ) + differ.replace(sortedFiles) - itemBinder.bindLivePhotoIndicator(holder, file) + displayedSortOrder = sortOrder + } - if (!MDMConfig.sharingSupport(activity)) { - holder.shared.visibility = View.GONE - } + override fun addCheckedFile(file: OCFile) = ocFileListDelegate.addCheckedFile(file) - bindMoreOptionVisibility(holder) - holder.fileFeaturesLayout?.setVisibleIf(holder.hasVisibleFeatureIndicators) - } + override fun isCheckedFile(file: OCFile): Boolean = ocFileListDelegate.isCheckedFile(file) - private fun shouldShowItemOptions(): Boolean = !(isMultiSelect() || gridView || hideItemOptions) + override fun getCheckedItems(): Set = ocFileListDelegate.checkedItems - private fun bindMoreOptionVisibility(holder: ListViewHolder) { - val showMoreOptions = !isMultiSelect() && !OCFileListFragment.isMultipleFileSelectedForCopyOrMove + override fun removeCheckedFile(file: OCFile) = ocFileListDelegate.removeCheckedFile(file) - if (holder is ListItemViewHolder) { - holder.overflowMenu.setVisibleIf(showMoreOptions) + override fun notifyItemChanged(file: OCFile) { + val recommendedAdapter = visibleRecommendedFilesAdapterFor(file) + if (recommendedAdapter != null) { + recommendedAdapter.notifyItemChanged(recommendedAdapter.getItemPosition(file)) } else { - holder.more.setVisibleIf(showMoreOptions) + notifyItemChangedIfPresent(file) } } - private fun shouldShowRecommendedFiles(): Boolean = - recommendedFiles.isNotEmpty() && currentDirectory?.isRootDirectory == true + override fun getFilesCount(): Int = files.size - private fun shouldShowOpenInNotes(): Boolean { - if (!preferences.isShowEcosystemApps) { - return false - } + override fun clearCheckedItems() = ocFileListDelegate.clearCheckedItems() - val notesFolderPath = capability.notesFolderPath - val currentPath = currentDirectory?.decryptedRemotePath - return notesFolderPath != null && currentPath?.startsWith(notesFolderPath) == true + override fun selectAll(value: Boolean) { + if (value) { + ocFileListDelegate.addToCheckedFiles(files) + } else { + clearCheckedItems() + } } - private fun visibleRecommendedFilesAdapterFor(file: OCFile): RecommendedFilesAdapter? = - recommendedFilesAdapter?.takeIf { shouldShowRecommendedFiles() && file.isRecommendedFile } - - @SuppressLint("NotifyDataSetChanged") - fun updateRecommendedFiles(value: List) { - recommendedFiles.clear() - recommendedFiles.addAll(value) - differ.syncHeaderState() - notifyDataSetChanged() + override fun avatarGenerated(avatarDrawable: Drawable?, callContext: Any?) { + (callContext as ImageView).setImageDrawable(avatarDrawable) } - override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder) { - val thumbnailShimmer = (holder as? ListViewHolder)?.shimmerThumbnail ?: return - if (thumbnailShimmer.isVisible) { - thumbnailShimmer.setImageResource(R.drawable.background) - thumbnailShimmer.resetLoader() + override fun shouldCallGeneratedCallback(tag: String?, callContext: Any?): Boolean = + (callContext as ImageView).tag == tag + + override fun getPopupText(view: View, position: Int): String { + val file = getItem(position) + val sortOrder = displayedSortOrder + if (file == null || sortOrder == null) { + return "" } - } - override fun onViewRecycled(holder: RecyclerView.ViewHolder) { - super.onViewRecycled(holder) - if (holder is ListViewHolder) { - holder.thumbnail.stopShimmer(holder.shimmerThumbnail) + return when (sortOrder.getType()) { + SortType.ALPHABET -> file.fileName.take(1).uppercase(Locale.getDefault()) + SortType.DATE -> popupDateFormat.format(Date(file.modificationTimestamp)) + SortType.SIZE -> HumanReadableFormatter.formatBytes(file.fileLength) } } + // endregion - private fun footerText(): String { - val showHiddenFiles = preferences.isShowHiddenFilesEnabled - val foldersCount = files.count { it.isFolder } - val filesCount = files.count { !it.isFolder && (!it.isHidden || showHiddenFiles) } - val resources = activity.resources + // region Public methods + fun getItem(position: Int): OCFile? = files.getOrNull(position - differ.headerOffset()) - return listOfNotNull( - filesCount.takeIf { it > 0 }?.let { - resources.getQuantityString(R.plurals.file_list__footer__file, it, it) - }, - foldersCount.takeIf { it > 0 }?.let { - resources.getQuantityString(R.plurals.file_list__footer__folder, it, it) - } - ).joinToString(FOOTER_TEXT_SEPARATOR) - } + fun bindRecommendedFilesHolder(holder: OCFileListRecommendedItemViewHolder, file: OCFile) = bindFile(holder, file) fun shouldShowHeader(): Boolean { val directory = currentDirectory @@ -470,45 +376,8 @@ class OCFileListAdapter( } return shouldShowRecommendedFiles() || - shouldShowOpenInNotes() || - directory.richWorkspace?.any { it > ' ' } == true - } - - /** - * Change the adapted directory for a new one - * - * @param directory New folder to adapt. - * @param storageManager Replaces the current storage manager when it differs from it. - * @param mLimitToMimeType show only files of this mimeType - */ - override fun swapDirectory( - user: User, - directory: OCFile, - storageManager: FileDataStorageManager, - onlyOnDevice: Boolean, - mLimitToMimeType: String - ) { - this.onlyOnDevice = onlyOnDevice - - if (storageManager != this.storageManager) { - this.storageManager = storageManager - adapterDataProvider = OCFileListAdapterDataProviderImpl(storageManager) - ocFileListDelegate.setShowShareAvatar(true) - this.user = user - } - - val accountUserId = userId ?: return - - helper.prepareFileList( - directory, - adapterDataProvider, - onlyOnDevice, - mLimitToMimeType, - preferences, - accountUserId - ) { newList, _ -> - updateAdapter(newList, directory) - } + headerBinder.shouldShowOpenInNotes(directory) || + headerBinder.hasRichWorkspace(directory) } fun updateAdapter(newFiles: List, directory: OCFile?) { @@ -551,34 +420,24 @@ class OCFileListAdapter( } } - fun setSortOrder(newSortOrder: FileSortOrder?) { - sortOrder = newSortOrder + fun setDisplayedSortOrder(sortOrder: FileSortOrder?) { + displayedSortOrder = sortOrder } - override fun setSortOrder(mFile: OCFile?, sortOrder: FileSortOrder) { - if (searchType == SearchType.FAVORITE_SEARCH) { - preferences.setSortOrder(FileSortOrder.Type.favoritesListView, sortOrder) - } else { - preferences.setSortOrder(mFile, sortOrder) - } - - val sortedFiles = sortOrder.sortCloudFiles( - differ.latestFiles.toMutableList(), - preferences.isSortFoldersBeforeFiles, - preferences.isSortFavoritesFirst - ) - differ.replace(sortedFiles) - - this.sortOrder = sortOrder - } + fun setCheckedItem(files: Set?) = ocFileListDelegate.setCheckedItem(files) - override fun getCheckedItems(): Set = ocFileListDelegate.checkedItems + fun setFiles(files: List) = differ.submit(files.toList()) - fun setCheckedItem(files: Set?) = ocFileListDelegate.setCheckedItem(files) + fun getFileByRemoteId(remoteId: String?): OCFile? = allFiles.firstOrNull { it.remoteId == remoteId } - override fun clearCheckedItems() = ocFileListDelegate.clearCheckedItems() + fun insertFile(file: OCFile?) { + if (file == null || file in allFiles) { + return + } - fun setFiles(files: List) = differ.submit(files.toList()) + allFiles.add(file) + differ.submit(differ.latestFiles + file) + } fun replaceFile(file: OCFile) { val index = differ.latestFiles.indexOf(file) @@ -591,145 +450,204 @@ class OCFileListAdapter( notifyItemChanged(file) } - fun getFileByRemoteId(fileId: String?): OCFile? = allFiles.firstOrNull { it.remoteId == fileId } + fun updateFile(updatedFile: OCFile) { + val allIndex = helper.indexOfSameRemoteFile(allFiles, updatedFile) + if (allIndex != RecyclerView.NO_POSITION) { + allFiles[allIndex] = updatedFile + } - fun insertFile(file: OCFile?) { - if (file == null || file in allFiles) { + val newFiles = differ.latestFiles.toMutableList() + val oldIndex = helper.indexOfSameRemoteFile(newFiles, updatedFile) + if (oldIndex == RecyclerView.NO_POSITION) { return } - allFiles.add(file) - differ.submit(differ.latestFiles + file) - } + newFiles.removeAt(oldIndex) + newFiles.add(updatedFile) + differ.submit(differ.sortForCurrentView(newFiles, searchType, currentDirectory)) - override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { - super.onAttachedToRecyclerView(recyclerView) - differ.attach(recyclerView) + visibleRecommendedFilesAdapterFor(updatedFile)?.let { adapter -> + val position = adapter.getItemPosition(updatedFile) + if (position != RecyclerView.NO_POSITION) { + adapter.notifyItemChanged(position) + } + } } - override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { - super.onDetachedFromRecyclerView(recyclerView) - differ.detach() + fun removeFile(file: OCFile) { + allFiles.remove(file) + differ.submit(differ.latestFiles - file) } - override fun avatarGenerated(avatarDrawable: Drawable?, callContext: Any?) { - (callContext as ImageView).setImageDrawable(avatarDrawable) + fun removeAllFiles() { + allFiles.clear() + differ.submit(emptyList()) } - override fun shouldCallGeneratedCallback(tag: String?, callContext: Any?): Boolean = - (callContext as ImageView).tag == tag - - override fun isCheckedFile(file: OCFile): Boolean = ocFileListDelegate.isCheckedFile(file) + fun updateFavoriteState(remotePath: String?, isFavorite: Boolean, removeFromList: Boolean) { + val filesToDelete = mutableListOf() - override fun addCheckedFile(file: OCFile) = ocFileListDelegate.addCheckedFile(file) + val changedFile = differ.latestFiles.firstOrNull { it.remotePath == remotePath }?.also { file -> + file.isFavorite = isFavorite + if (removeFromList) { + filesToDelete.add(file) + } + } - override fun setHighlightedItem(file: OCFile) = ocFileListDelegate.setHighlightedItem(file) + allFiles.firstOrNull { it.remotePath == remotePath }?.let { file -> + file.isFavorite = isFavorite + storageManager.saveFile(file) + if (removeFromList) { + filesToDelete.add(file) + } + } - override fun cancelAllPendingTasks() = thumbnailGenerator.fileThumbnailGenerator.cancelPendingTasks() + mainHandler.post { + val sortedFiles = differ.sortForCurrentView( + differ.latestFiles.toMutableList(), + searchType, + currentDirectory + ) + sortedFiles.removeAll(filesToDelete) + differ.submit(sortedFiles) - fun setGridView(bool: Boolean) { - gridView = bool + if (changedFile != null && !removeFromList) { + notifyItemChangedIfPresent(changedFile) + } + } } - fun setShowMetadata(bool: Boolean) { - ocFileListDelegate.isMultiSelect = bool + @SuppressLint("NotifyDataSetChanged") + fun markCommentsAsRead(remoteId: String?) { + files.firstOrNull { it.remoteId == remoteId }?.unreadCommentsCount = 0 + allFiles.firstOrNull { it.remoteId == remoteId }?.unreadCommentsCount = 0 + mainHandler.post { notifyDataSetChanged() } } - override fun getPopupText(view: View, position: Int): String { - val file = getItem(position) - val sortOrder = sortOrder - if (file == null || sortOrder == null) { - return "" - } + fun updateFileEncryptionById(remoteId: String?, encrypted: Boolean) { + val file = remoteId?.let { id -> allFiles.firstOrNull { it.remoteId == id } } ?: return - return when (sortOrder.getType()) { - SortType.ALPHABET -> file.fileName.take(1).uppercase(Locale.getDefault()) - SortType.DATE -> dateFormat.format(Date(file.modificationTimestamp)) - SortType.SIZE -> HumanReadableFormatter.formatBytes(file.fileLength) + file.isEncrypted = encrypted + val e2eCounter = if (E2EVersionHelper.isV2Plus(capability)) { + EncryptionUtils.E2E_V2_INITIAL_COUNTER + } else { + EncryptionUtils.E2E_V1_INITIAL_COUNTER } + storageManager.updateE2EECounter(file, e2eCounter) + notifyItemChangedIfPresent(file) } - @VisibleForTesting - fun setShowShareAvatar(bool: Boolean) = ocFileListDelegate.setShowShareAvatar(bool) + fun updateFileIndicator(iconId: Int, file: OCFile?) { + file?.let { notifyItemChangedIfPresent(it, iconId) } + } - override fun getFilesCount(): Int = files.size + @SuppressLint("NotifyDataSetChanged") + fun updateRecommendedFiles(value: List) { + recommendedFiles.clear() + recommendedFiles.addAll(value) + differ.syncHeaderState() + notifyDataSetChanged() + } - override fun notifyItemChanged(file: OCFile) { - val recommendedAdapter = visibleRecommendedFilesAdapterFor(file) - if (recommendedAdapter != null) { - recommendedAdapter.notifyItemChanged(recommendedAdapter.getItemPosition(file)) - } else { - notifyItemChangedIfPresent(file) - } + fun setGridView(bool: Boolean) { + gridView = bool } - private fun notifyItemChangedIfPresent(file: OCFile, payload: Any? = null) { - val position = getItemPosition(file) - if (position != RecyclerView.NO_POSITION) { - notifyItemChanged(position, payload) - } + fun setShowMetadata(bool: Boolean) { + ocFileListDelegate.isMultiSelect = bool } + @VisibleForTesting + fun setShowShareAvatar(bool: Boolean) = ocFileListDelegate.setShowShareAvatar(bool) + @VisibleForTesting fun setCurrentDirectory(folder: OCFile?) { currentDirectory = folder differ.syncHeaderState() } - fun updateFileIndicator(iconId: Int, file: OCFile?) { - file?.let { notifyItemChangedIfPresent(it, iconId) } - } - fun cleanup() { ocFileListDelegate.cleanup() helper.cleanup() } + // endregion - fun removeAllFiles() { - allFiles.clear() - differ.submit(emptyList()) + // region Private methods + private fun viewTypeAt(position: Int): OCFileListViewType = when { + isHeaderDisplayed && position == 0 -> OCFileListViewType.HEADER + position - differ.headerOffset() == files.size -> OCFileListViewType.FOOTER + getItem(position)?.let { MimeTypeUtil.isImageOrVideo(it) } == true -> OCFileListViewType.IMAGE + else -> OCFileListViewType.ITEM } - fun removeFile(file: OCFile) { - allFiles.remove(file) - differ.submit(differ.latestFiles - file) + private fun bindHeader(holder: OCFileListHeaderViewHolder) { + val visibleRecommendedFilesAdapter = if (shouldShowRecommendedFiles()) { + RecommendedFilesAdapter(this, recommendedFiles).also { recommendedFilesAdapter = it } + } else { + null + } + headerBinder.bind(holder, currentDirectory, visibleRecommendedFilesAdapter) } - fun updateFile(updatedFile: OCFile) { - val allIndex = helper.indexOfSameRemoteFile(allFiles, updatedFile) - if (allIndex != RecyclerView.NO_POSITION) { - allFiles[allIndex] = updatedFile + private fun bindFileAt(holder: ListViewHolder, position: Int) { + val file = getItem(position) + if (file == null) { + Log_OC.e(this, "Cannot bind on view holder on a null file") + return } + bindFile(holder, file) + } - val newFiles = differ.latestFiles.toMutableList() - val oldIndex = helper.indexOfSameRemoteFile(newFiles, updatedFile) - if (oldIndex == RecyclerView.NO_POSITION) { - return + private fun bindFile(holder: ListViewHolder, file: OCFile) { + ocFileListDelegate.bindViewHolder(holder, file, currentDirectory, searchType, thumbnailGenerator) + + if (holder is ListItemViewHolder) { + itemBinder.bindDetails(holder, file, user, storageManager, shouldShowItemOptions(), onlyOnDevice) } - newFiles.removeAt(oldIndex) - newFiles.add(updatedFile) - differ.submit(differ.sortForCurrentView(newFiles, searchType, currentDirectory)) + if (holder is ListGridItemViewHolder) { + itemBinder.bindFilename(holder, file, storageManager.getFilenameConsideringOfflineOperation(file)) + } - visibleRecommendedFilesAdapterFor(updatedFile)?.let { adapter -> - val position = adapter.getItemPosition(updatedFile) - if (position != RecyclerView.NO_POSITION) { - adapter.notifyItemChanged(position) - } + itemBinder.bindLivePhotoIndicator(holder, file) + + if (!MDMConfig.sharingSupport(activity)) { + holder.shared.visibility = View.GONE + } + + bindMoreOptionsVisibility(holder) + holder.fileFeaturesLayout?.setVisibleIf(holder.hasVisibleFeatureIndicators) + } + + private fun shouldShowItemOptions(): Boolean = !(isMultiSelect() || gridView || hideItemOptions) + + private fun bindMoreOptionsVisibility(holder: ListViewHolder) { + val showMoreOptions = !isMultiSelect() && !OCFileListFragment.isMultipleFileSelectedForCopyOrMove + + if (holder is ListItemViewHolder) { + holder.overflowMenu.setVisibleIf(showMoreOptions) + } else { + holder.more.setVisibleIf(showMoreOptions) } } + private fun shouldShowRecommendedFiles(): Boolean = + recommendedFiles.isNotEmpty() && currentDirectory?.isRootDirectory == true + + private fun visibleRecommendedFilesAdapterFor(file: OCFile): RecommendedFilesAdapter? = + recommendedFilesAdapter?.takeIf { shouldShowRecommendedFiles() && file.isRecommendedFile } + + private fun notifyItemChangedIfPresent(file: OCFile, payload: Any? = null) { + val position = getItemPosition(file) + if (position != RecyclerView.NO_POSITION) { + notifyItemChanged(position, payload) + } + } + // endregion + companion object { private val TAG = OCFileListAdapter::class.java.simpleName - private const val VIEW_TYPE_FOOTER = 0 - private const val VIEW_TYPE_ITEM = 1 - private const val VIEW_TYPE_IMAGE = 2 - private const val VIEW_TYPE_HEADER = 3 - private const val FOOTER_ITEM_COUNT = 1 - private const val FOOTER_TEXT_SEPARATOR = ", " - private const val EMPTY_RICH_WORKSPACE = " " - private const val POPUP_DATE_FORMAT = "MMMM yyyy" } } diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListFooterBinder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListFooterBinder.kt new file mode 100644 index 000000000000..ff0713b9a9a8 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListFooterBinder.kt @@ -0,0 +1,54 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.owncloud.android.ui.adapter.filelist + +import android.app.Activity +import com.nextcloud.android.common.ui.theme.utils.ColorRole +import com.nextcloud.client.preferences.AppPreferences +import com.nextcloud.utils.extensions.setVisibleIf +import com.owncloud.android.R +import com.owncloud.android.datamodel.OCFile +import com.owncloud.android.ui.adapter.OCFileListFooterViewHolder +import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface +import com.owncloud.android.utils.theme.ViewThemeUtils + +internal class OCFileListFooterBinder( + private val activity: Activity, + private val viewThemeUtils: ViewThemeUtils, + private val preferences: AppPreferences, + private val fragmentInterface: OCFileListFragmentInterface +) { + + // region Public methods + fun bind(holder: OCFileListFooterViewHolder, files: List) { + holder.footerText.text = fileCountSummary(files) + viewThemeUtils.platform.colorCircularProgressBar(holder.loadingProgressBar, ColorRole.ON_SURFACE_VARIANT) + holder.loadingProgressBar.setVisibleIf(fragmentInterface.isLoading) + } + // endregion + + // region Private methods + private fun fileCountSummary(files: List): String { + val showHiddenFiles = preferences.isShowHiddenFilesEnabled + val foldersCount = files.count { it.isFolder } + val filesCount = files.count { !it.isFolder && (!it.isHidden || showHiddenFiles) } + + return listOfNotNull( + quantityText(R.plurals.file_list__footer__file, filesCount), + quantityText(R.plurals.file_list__footer__folder, foldersCount) + ).joinToString(SUMMARY_SEPARATOR) + } + + private fun quantityText(pluralsRes: Int, count: Int): String? = count.takeIf { it > 0 }?.let { + activity.resources.getQuantityString(pluralsRes, it, it) + } + // endregion + + companion object { + private const val SUMMARY_SEPARATOR = ", " + } +} diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListHeaderBinder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListHeaderBinder.kt new file mode 100644 index 000000000000..9d4f646d2a94 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListHeaderBinder.kt @@ -0,0 +1,120 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.owncloud.android.ui.adapter.filelist + +import android.app.Activity +import androidx.recyclerview.widget.LinearLayoutManager +import com.nextcloud.android.common.core.utils.ecosystem.EcosystemApp +import com.nextcloud.client.account.User +import com.nextcloud.client.preferences.AppPreferences +import com.nextcloud.utils.extensions.setVisibleIf +import com.owncloud.android.R +import com.owncloud.android.databinding.ListHeaderBinding +import com.owncloud.android.databinding.ListHeaderOpenInBinding +import com.owncloud.android.datamodel.OCFile +import com.owncloud.android.lib.resources.status.OCCapability +import com.owncloud.android.ui.activity.DrawerActivity +import com.owncloud.android.ui.adapter.OCFileListHeaderViewHolder +import com.owncloud.android.ui.adapter.RecommendedFilesAdapter +import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface +import com.owncloud.android.ui.preview.PreviewTextFragment +import com.owncloud.android.utils.theme.ViewThemeUtils + +@Suppress("LongParameterList") +internal class OCFileListHeaderBinder( + private val activity: Activity, + private val viewThemeUtils: ViewThemeUtils, + private val fragmentInterface: OCFileListFragmentInterface, + private val preferences: AppPreferences, + private val capability: OCCapability, + private val currentUser: () -> User +) { + + // region Public methods + fun bind( + holder: OCFileListHeaderViewHolder, + directory: OCFile?, + recommendedFilesAdapter: RecommendedFilesAdapter? + ) { + holder.headerView.setOnClickListener { fragmentInterface.onHeaderClicked() } + bindRichWorkspace(holder, directory) + bindRecommendedFiles(holder.binding, recommendedFilesAdapter) + bindOpenInNotes(holder.binding.openIn, directory) + } + + fun hasRichWorkspace(directory: OCFile): Boolean = directory.richWorkspace?.any { it > ' ' } == true + + fun shouldShowOpenInNotes(directory: OCFile?): Boolean { + if (!preferences.isShowEcosystemApps) { + return false + } + + val notesFolderPath = capability.notesFolderPath + val currentPath = directory?.decryptedRemotePath + return notesFolderPath != null && currentPath?.startsWith(notesFolderPath) == true + } + // endregion + + // region Private methods + private fun bindRichWorkspace(holder: OCFileListHeaderViewHolder, directory: OCFile?) { + val text = directory?.richWorkspace + PreviewTextFragment.setText( + holder.headerText, + text, + null, + activity, + ignoreMimetype = true, + preview = true, + viewThemeUtils + ) + holder.headerText.setVisibleIf(isRichWorkspaceTextVisible(text)) + } + + private fun isRichWorkspaceTextVisible(text: String?): Boolean = + text != null && text != EMPTY_RICH_WORKSPACE && text.any { !Character.isWhitespace(it) } + + private fun bindRecommendedFiles(binding: ListHeaderBinding, recommendedFilesAdapter: RecommendedFilesAdapter?) { + val showRecommendedFiles = recommendedFilesAdapter != null + binding.recommendedFilesRecyclerView.setVisibleIf(showRecommendedFiles) + binding.recommendedFilesTitle.setVisibleIf(showRecommendedFiles) + binding.allFilesTitle.setVisibleIf(showRecommendedFiles) + + if (recommendedFilesAdapter == null) { + return + } + + binding.recommendedFilesRecyclerView.run { + layoutManager = LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false) + adapter = recommendedFilesAdapter + } + } + + private fun bindOpenInNotes(binding: ListHeaderOpenInBinding, directory: OCFile?) { + val showOpenInNotes = shouldShowOpenInNotes(directory) + binding.root.setVisibleIf(showOpenInNotes) + + if (!showOpenInNotes) { + return + } + + viewThemeUtils.files.themeFilledCardView(binding.infoCard) + binding.infoText.text = activity.getString(R.string.folder_best_viewed_in) + .format(activity.getString(R.string.ecosystem_apps_notes)) + binding.openInButton.text = activity.getString(R.string.open_in_app) + .format(activity.getString(R.string.ecosystem_apps_display_notes)) + + val ecosystemManager = (activity as? DrawerActivity)?.ecosystemManager ?: return + binding.openInButton.setOnClickListener { + ecosystemManager.openApp(EcosystemApp.NOTES, currentUser().accountName) + } + } + // endregion + + companion object { + private const val EMPTY_RICH_WORKSPACE = " " + } +} diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListItemBinder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListItemBinder.kt index 7b7a2d655d1a..a9469c1e3d45 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListItemBinder.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListItemBinder.kt @@ -44,6 +44,7 @@ internal class OCFileListItemBinder( private val avatarShareesProvider = AvatarShareesProvider() private val isRTL = LocaleDirection.isRtl + // region Public methods fun bindDetails( holder: ListItemViewHolder, file: OCFile, @@ -85,7 +86,9 @@ internal class OCFileListItemBinder( is OCFileListViewHolder -> holder.gridLivePhotoIndicator.setVisibleIf(isLivePhoto) } } + // endregion + // region Private methods private fun bindPlainFilename(holder: ListGridItemViewHolder, filename: String?) { holder.fileName.text = filename @@ -241,6 +244,7 @@ internal class OCFileListItemBinder( val iconRes = if (file.isLocked) R.drawable.ic_locked_dots_small else R.drawable.ic_dots_vertical overflowMenu.setImageResource(iconRes) } + // endregion companion object { private val TAG = OCFileListItemBinder::class.java.simpleName diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListViewType.kt b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListViewType.kt new file mode 100644 index 000000000000..ff68b3dcffb7 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListViewType.kt @@ -0,0 +1,18 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.owncloud.android.ui.adapter.filelist + +internal enum class OCFileListViewType { + FOOTER, + ITEM, + IMAGE, + HEADER; + + companion object { + fun fromOrdinal(ordinal: Int): OCFileListViewType = entries.getOrElse(ordinal) { ITEM } + } +} diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java index 3d6060dfb8fc..58389d12a2e5 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -1780,7 +1780,7 @@ public void resetSearchAttributes() { @Subscribe(threadMode = ThreadMode.BACKGROUND) public void onMessageEvent(CommentsEvent event) { - mAdapter.refreshCommentsCount(event.getRemoteId()); + mAdapter.markCommentsAsRead(event.getRemoteId()); } @Subscribe(threadMode = ThreadMode.BACKGROUND) @@ -1799,7 +1799,7 @@ public void onMessageEvent(FavoriteEvent event) { if (this instanceof GalleryFragment galleryFragment) { galleryFragment.markAsFavorite(event.getRemotePath(), event.getShouldFavorite()); } else { - mAdapter.setFavoriteAttributeForItemID(event.getRemotePath(), event.getShouldFavorite(), removeFromList); + mAdapter.updateFavoriteState(event.getRemotePath(), event.getShouldFavorite(), removeFromList); } } diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchTask.kt b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchTask.kt index bf8104ff4899..3696d9246dc5 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchTask.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchTask.kt @@ -191,7 +191,7 @@ class OCFileListSearchTask( } } - fragment.adapter.setSortOrder(sortOrder) + fragment.adapter.setDisplayedSortOrder(sortOrder) return sortOrder.sortCloudFiles(list.toMutableList(), foldersBeforeFiles, favoritesFirst) } From ce5625c8314317c9823bd86e10cad0856566130e Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 13 Oct 2026 10:45:34 +0200 Subject: [PATCH 5/8] reorganize package Signed-off-by: alperozturk96 --- .../nextcloud/client/FileDisplayActivityIT.kt | 2 +- .../OCFileListAdapterDataProviderImpl.kt | 2 +- .../android/ui/adapter/GalleryAdapter.kt | 2 ++ .../android/ui/adapter/GalleryRowHolder.kt | 1 + .../ui/adapter/GroupFolderListAdapter.kt | 1 + .../CommonOCFileListAdapterInterface.kt | 2 +- .../{ => filelist}/OCFileListAdapter.kt | 27 ++++++++++++------- .../filelist/{ => data}/OCFileDiffCallback.kt | 2 +- .../OCFileListAdapterDataProvider.kt | 2 +- .../{ => data}/OCFileListAdapterHelper.kt | 2 +- .../filelist/{ => data}/OCFileListDiffer.kt | 2 +- .../{ => footer}/OCFileListFooterBinder.kt | 3 +-- .../footer}/OCFileListFooterViewHolder.kt | 3 ++- .../{ => header}/OCFileListHeaderBinder.kt | 5 ++-- .../header}/OCFileListHeaderViewHolder.kt | 3 ++- .../{ => item}/AvatarShareesProvider.kt | 2 +- .../item}/ListGridItemViewHolder.kt | 3 ++- .../{ => filelist/item}/ListItemViewHolder.kt | 3 ++- .../{ => filelist/item}/ListViewHolder.kt | 3 ++- .../{ => filelist/item}/OCFileListDelegate.kt | 7 +++-- .../item}/OCFileListGridItemViewHolder.kt | 3 ++- .../{ => item}/OCFileListItemBinder.kt | 8 +----- .../item}/OCFileListItemViewHolder.kt | 3 ++- .../item}/OCFileListViewHolder.kt | 3 ++- .../recommended}/RecommendedFilesAdapter.kt | 11 ++++---- .../RecommendedFilesViewHolder.kt} | 5 ++-- .../ui/fragment/ExtendedListFragment.kt | 2 +- .../ui/fragment/FileListLayoutManager.kt | 2 +- .../android/ui/fragment/GalleryFragment.kt | 2 +- .../ui/fragment/OCFileListFragment.java | 4 +-- .../OCFileListFragmentInterface.java | 2 +- .../MockOCFileListAdapterDataProvider.kt | 3 +-- .../data}/OCFileListAdapterHelperTest.kt | 3 +-- 33 files changed, 72 insertions(+), 56 deletions(-) rename app/src/main/java/com/owncloud/android/ui/adapter/{ => filelist}/CommonOCFileListAdapterInterface.kt (96%) rename app/src/main/java/com/owncloud/android/ui/adapter/{ => filelist}/OCFileListAdapter.kt (94%) rename app/src/main/java/com/owncloud/android/ui/adapter/filelist/{ => data}/OCFileDiffCallback.kt (97%) rename app/src/main/java/com/owncloud/android/ui/adapter/filelist/{ => data}/OCFileListAdapterDataProvider.kt (89%) rename app/src/main/java/com/owncloud/android/ui/adapter/filelist/{ => data}/OCFileListAdapterHelper.kt (99%) rename app/src/main/java/com/owncloud/android/ui/adapter/filelist/{ => data}/OCFileListDiffer.kt (98%) rename app/src/main/java/com/owncloud/android/ui/adapter/filelist/{ => footer}/OCFileListFooterBinder.kt (94%) rename app/src/main/java/com/owncloud/android/ui/adapter/{ => filelist/footer}/OCFileListFooterViewHolder.kt (81%) rename app/src/main/java/com/owncloud/android/ui/adapter/filelist/{ => header}/OCFileListHeaderBinder.kt (96%) rename app/src/main/java/com/owncloud/android/ui/adapter/{ => filelist/header}/OCFileListHeaderViewHolder.kt (81%) rename app/src/main/java/com/owncloud/android/ui/adapter/filelist/{ => item}/AvatarShareesProvider.kt (93%) rename app/src/main/java/com/owncloud/android/ui/adapter/{ => filelist/item}/ListGridItemViewHolder.kt (82%) rename app/src/main/java/com/owncloud/android/ui/adapter/{ => filelist/item}/ListItemViewHolder.kt (87%) rename app/src/main/java/com/owncloud/android/ui/adapter/{ => filelist/item}/ListViewHolder.kt (88%) rename app/src/main/java/com/owncloud/android/ui/adapter/{ => filelist/item}/OCFileListDelegate.kt (97%) rename app/src/main/java/com/owncloud/android/ui/adapter/{ => filelist/item}/OCFileListGridItemViewHolder.kt (95%) rename app/src/main/java/com/owncloud/android/ui/adapter/filelist/{ => item}/OCFileListItemBinder.kt (95%) rename app/src/main/java/com/owncloud/android/ui/adapter/{ => filelist/item}/OCFileListItemViewHolder.kt (96%) rename app/src/main/java/com/owncloud/android/ui/adapter/{ => filelist/item}/OCFileListViewHolder.kt (95%) rename app/src/main/java/com/owncloud/android/ui/adapter/{ => filelist/recommended}/RecommendedFilesAdapter.kt (73%) rename app/src/main/java/com/owncloud/android/ui/adapter/{OCFileListRecommendedItemViewHolder.kt => filelist/recommended/RecommendedFilesViewHolder.kt} (91%) rename app/src/test/java/com/owncloud/android/ui/adapter/{ => filelist/data}/MockOCFileListAdapterDataProvider.kt (96%) rename app/src/test/java/com/owncloud/android/ui/adapter/{ => filelist/data}/OCFileListAdapterHelperTest.kt (98%) diff --git a/app/src/androidTest/java/com/nextcloud/client/FileDisplayActivityIT.kt b/app/src/androidTest/java/com/nextcloud/client/FileDisplayActivityIT.kt index 0805ef65e365..00d7165d1380 100644 --- a/app/src/androidTest/java/com/nextcloud/client/FileDisplayActivityIT.kt +++ b/app/src/androidTest/java/com/nextcloud/client/FileDisplayActivityIT.kt @@ -37,7 +37,7 @@ import com.owncloud.android.lib.resources.shares.OCShare import com.owncloud.android.lib.resources.shares.ShareType import com.owncloud.android.operations.CreateFolderOperation import com.owncloud.android.ui.activity.FileDisplayActivity -import com.owncloud.android.ui.adapter.OCFileListItemViewHolder +import com.owncloud.android.ui.adapter.filelist.item.OCFileListItemViewHolder import com.owncloud.android.utils.EspressoIdlingResource import org.hamcrest.Matchers.allOf import org.junit.After diff --git a/app/src/main/java/com/owncloud/android/datamodel/OCFileListAdapterDataProviderImpl.kt b/app/src/main/java/com/owncloud/android/datamodel/OCFileListAdapterDataProviderImpl.kt index 994695b13ffc..8ad715e6eb7b 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/OCFileListAdapterDataProviderImpl.kt +++ b/app/src/main/java/com/owncloud/android/datamodel/OCFileListAdapterDataProviderImpl.kt @@ -8,7 +8,7 @@ package com.owncloud.android.datamodel import com.nextcloud.client.database.entity.FileEntity -import com.owncloud.android.ui.adapter.filelist.OCFileListAdapterDataProvider +import com.owncloud.android.ui.adapter.filelist.data.OCFileListAdapterDataProvider @Suppress("ReturnCount") class OCFileListAdapterDataProviderImpl(private val storageManager: FileDataStorageManager) : diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/GalleryAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/GalleryAdapter.kt index f6ee043f9710..ef6c68210555 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/GalleryAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/GalleryAdapter.kt @@ -37,6 +37,8 @@ import com.owncloud.android.datamodel.GalleryRowLayout import com.owncloud.android.datamodel.OCFile import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.ui.activity.ComponentsGetter +import com.owncloud.android.ui.adapter.filelist.CommonOCFileListAdapterInterface +import com.owncloud.android.ui.adapter.filelist.item.OCFileListDelegate import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface import com.owncloud.android.utils.FileSortOrder import com.owncloud.android.utils.theme.ViewThemeUtils diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/GalleryRowHolder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/GalleryRowHolder.kt index 76f7416b597d..8d3e2142f0ad 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/GalleryRowHolder.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/GalleryRowHolder.kt @@ -25,6 +25,7 @@ import com.owncloud.android.databinding.GalleryRowBinding import com.owncloud.android.datamodel.GalleryCellSize import com.owncloud.android.datamodel.GalleryRow import com.owncloud.android.datamodel.OCFile +import com.owncloud.android.ui.adapter.filelist.item.OCFileListDelegate import com.owncloud.android.utils.theme.ViewThemeUtils private const val CHECKED_SCALE = 0.8f diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/GroupFolderListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/GroupFolderListAdapter.kt index 19d05b70a6b0..cab44072a689 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/GroupFolderListAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/GroupFolderListAdapter.kt @@ -17,6 +17,7 @@ import androidx.recyclerview.widget.RecyclerView import com.nextcloud.android.lib.resources.groupfolders.Groupfolder import com.owncloud.android.R import com.owncloud.android.databinding.ListItemBinding +import com.owncloud.android.ui.adapter.filelist.item.OCFileListItemViewHolder import com.owncloud.android.ui.interfaces.GroupfolderListInterface import com.owncloud.android.utils.MimeTypeUtil import com.owncloud.android.utils.theme.ViewThemeUtils diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/CommonOCFileListAdapterInterface.kt b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/CommonOCFileListAdapterInterface.kt similarity index 96% rename from app/src/main/java/com/owncloud/android/ui/adapter/CommonOCFileListAdapterInterface.kt rename to app/src/main/java/com/owncloud/android/ui/adapter/filelist/CommonOCFileListAdapterInterface.kt index e9b1651b9d58..a8d3152c1984 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/CommonOCFileListAdapterInterface.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/CommonOCFileListAdapterInterface.kt @@ -5,7 +5,7 @@ * SPDX-FileCopyrightText: 2022 Nextcloud GmbH * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only */ -package com.owncloud.android.ui.adapter +package com.owncloud.android.ui.adapter.filelist import com.nextcloud.client.account.User import com.owncloud.android.datamodel.FileDataStorageManager diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListAdapter.kt similarity index 94% rename from app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.kt rename to app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListAdapter.kt index baed5b64e7b8..3cb2a83e7f2a 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListAdapter.kt @@ -8,7 +8,7 @@ * SPDX-FileCopyrightText: 2018 Nextcloud * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only */ -package com.owncloud.android.ui.adapter +package com.owncloud.android.ui.adapter.filelist import android.accounts.AccountManager import android.annotation.SuppressLint @@ -52,13 +52,22 @@ import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.lib.resources.status.OCCapability import com.owncloud.android.ui.activity.ComponentsGetter import com.owncloud.android.ui.activity.FileDisplayActivity -import com.owncloud.android.ui.adapter.filelist.OCFileListAdapterDataProvider -import com.owncloud.android.ui.adapter.filelist.OCFileListAdapterHelper -import com.owncloud.android.ui.adapter.filelist.OCFileListDiffer -import com.owncloud.android.ui.adapter.filelist.OCFileListFooterBinder -import com.owncloud.android.ui.adapter.filelist.OCFileListHeaderBinder -import com.owncloud.android.ui.adapter.filelist.OCFileListItemBinder -import com.owncloud.android.ui.adapter.filelist.OCFileListViewType +import com.owncloud.android.ui.adapter.filelist.data.OCFileListAdapterDataProvider +import com.owncloud.android.ui.adapter.filelist.data.OCFileListAdapterHelper +import com.owncloud.android.ui.adapter.filelist.data.OCFileListDiffer +import com.owncloud.android.ui.adapter.filelist.footer.OCFileListFooterBinder +import com.owncloud.android.ui.adapter.filelist.footer.OCFileListFooterViewHolder +import com.owncloud.android.ui.adapter.filelist.header.OCFileListHeaderBinder +import com.owncloud.android.ui.adapter.filelist.header.OCFileListHeaderViewHolder +import com.owncloud.android.ui.adapter.filelist.item.ListGridItemViewHolder +import com.owncloud.android.ui.adapter.filelist.item.ListItemViewHolder +import com.owncloud.android.ui.adapter.filelist.item.ListViewHolder +import com.owncloud.android.ui.adapter.filelist.item.OCFileListDelegate +import com.owncloud.android.ui.adapter.filelist.item.OCFileListGridItemViewHolder +import com.owncloud.android.ui.adapter.filelist.item.OCFileListItemBinder +import com.owncloud.android.ui.adapter.filelist.item.OCFileListItemViewHolder +import com.owncloud.android.ui.adapter.filelist.recommended.RecommendedFilesViewHolder +import com.owncloud.android.ui.adapter.filelist.recommended.RecommendedFilesAdapter import com.owncloud.android.ui.fragment.OCFileListFragment import com.owncloud.android.ui.fragment.SearchType import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface @@ -367,7 +376,7 @@ class OCFileListAdapter( // region Public methods fun getItem(position: Int): OCFile? = files.getOrNull(position - differ.headerOffset()) - fun bindRecommendedFilesHolder(holder: OCFileListRecommendedItemViewHolder, file: OCFile) = bindFile(holder, file) + fun bindRecommendedFilesHolder(holder: RecommendedFilesViewHolder, file: OCFile) = bindFile(holder, file) fun shouldShowHeader(): Boolean { val directory = currentDirectory diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileDiffCallback.kt b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/data/OCFileDiffCallback.kt similarity index 97% rename from app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileDiffCallback.kt rename to app/src/main/java/com/owncloud/android/ui/adapter/filelist/data/OCFileDiffCallback.kt index 16b421d3465c..d8b68aef3539 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileDiffCallback.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/data/OCFileDiffCallback.kt @@ -4,7 +4,7 @@ * SPDX-FileCopyrightText: 2026 Alper Ozturk * SPDX-License-Identifier: AGPL-3.0-or-later */ -package com.owncloud.android.ui.adapter.filelist +package com.owncloud.android.ui.adapter.filelist.data import androidx.recyclerview.widget.DiffUtil import com.owncloud.android.datamodel.OCFile diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListAdapterDataProvider.kt b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/data/OCFileListAdapterDataProvider.kt similarity index 89% rename from app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListAdapterDataProvider.kt rename to app/src/main/java/com/owncloud/android/ui/adapter/filelist/data/OCFileListAdapterDataProvider.kt index 4ae74c333dbc..3dc0f99380db 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListAdapterDataProvider.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/data/OCFileListAdapterDataProvider.kt @@ -5,7 +5,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -package com.owncloud.android.ui.adapter.filelist +package com.owncloud.android.ui.adapter.filelist.data import com.nextcloud.client.database.entity.FileEntity import com.owncloud.android.datamodel.OCFile diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListAdapterHelper.kt b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/data/OCFileListAdapterHelper.kt similarity index 99% rename from app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListAdapterHelper.kt rename to app/src/main/java/com/owncloud/android/ui/adapter/filelist/data/OCFileListAdapterHelper.kt index d096c0db5ec4..5a3d335bbe01 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListAdapterHelper.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/data/OCFileListAdapterHelper.kt @@ -5,7 +5,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -package com.owncloud.android.ui.adapter.filelist +package com.owncloud.android.ui.adapter.filelist.data import com.nextcloud.client.database.entity.FileEntity import com.nextcloud.client.preferences.AppPreferences diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListDiffer.kt b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/data/OCFileListDiffer.kt similarity index 98% rename from app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListDiffer.kt rename to app/src/main/java/com/owncloud/android/ui/adapter/filelist/data/OCFileListDiffer.kt index 568948238254..708ed108f3bd 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListDiffer.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/data/OCFileListDiffer.kt @@ -4,7 +4,7 @@ * SPDX-FileCopyrightText: 2026 Alper Ozturk * SPDX-License-Identifier: AGPL-3.0-or-later */ -package com.owncloud.android.ui.adapter.filelist +package com.owncloud.android.ui.adapter.filelist.data import android.annotation.SuppressLint import androidx.recyclerview.widget.AsyncDifferConfig diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListFooterBinder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/footer/OCFileListFooterBinder.kt similarity index 94% rename from app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListFooterBinder.kt rename to app/src/main/java/com/owncloud/android/ui/adapter/filelist/footer/OCFileListFooterBinder.kt index ff0713b9a9a8..97ba1787e172 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListFooterBinder.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/footer/OCFileListFooterBinder.kt @@ -4,7 +4,7 @@ * SPDX-FileCopyrightText: 2026 Alper Ozturk * SPDX-License-Identifier: AGPL-3.0-or-later */ -package com.owncloud.android.ui.adapter.filelist +package com.owncloud.android.ui.adapter.filelist.footer import android.app.Activity import com.nextcloud.android.common.ui.theme.utils.ColorRole @@ -12,7 +12,6 @@ import com.nextcloud.client.preferences.AppPreferences import com.nextcloud.utils.extensions.setVisibleIf import com.owncloud.android.R import com.owncloud.android.datamodel.OCFile -import com.owncloud.android.ui.adapter.OCFileListFooterViewHolder import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface import com.owncloud.android.utils.theme.ViewThemeUtils diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListFooterViewHolder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/footer/OCFileListFooterViewHolder.kt similarity index 81% rename from app/src/main/java/com/owncloud/android/ui/adapter/OCFileListFooterViewHolder.kt rename to app/src/main/java/com/owncloud/android/ui/adapter/filelist/footer/OCFileListFooterViewHolder.kt index 33878df114ad..b1f9bdde9c71 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListFooterViewHolder.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/footer/OCFileListFooterViewHolder.kt @@ -1,11 +1,12 @@ /* * Nextcloud - Android Client * + * SPDX-FileCopyrightText: 2026 Alper Ozturk * SPDX-FileCopyrightText: 2022 Tobias Kaminsky * SPDX-FileCopyrightText: 2022 Nextcloud GmbH * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only */ -package com.owncloud.android.ui.adapter +package com.owncloud.android.ui.adapter.filelist.footer import androidx.recyclerview.widget.RecyclerView import com.owncloud.android.databinding.ListFooterBinding diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListHeaderBinder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/header/OCFileListHeaderBinder.kt similarity index 96% rename from app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListHeaderBinder.kt rename to app/src/main/java/com/owncloud/android/ui/adapter/filelist/header/OCFileListHeaderBinder.kt index 9d4f646d2a94..eb8b7b65e794 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListHeaderBinder.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/header/OCFileListHeaderBinder.kt @@ -4,7 +4,7 @@ * SPDX-FileCopyrightText: 2026 Alper Ozturk * SPDX-License-Identifier: AGPL-3.0-or-later */ -package com.owncloud.android.ui.adapter.filelist +package com.owncloud.android.ui.adapter.filelist.header import android.app.Activity import androidx.recyclerview.widget.LinearLayoutManager @@ -18,8 +18,7 @@ import com.owncloud.android.databinding.ListHeaderOpenInBinding import com.owncloud.android.datamodel.OCFile import com.owncloud.android.lib.resources.status.OCCapability import com.owncloud.android.ui.activity.DrawerActivity -import com.owncloud.android.ui.adapter.OCFileListHeaderViewHolder -import com.owncloud.android.ui.adapter.RecommendedFilesAdapter +import com.owncloud.android.ui.adapter.filelist.recommended.RecommendedFilesAdapter import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface import com.owncloud.android.ui.preview.PreviewTextFragment import com.owncloud.android.utils.theme.ViewThemeUtils diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListHeaderViewHolder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/header/OCFileListHeaderViewHolder.kt similarity index 81% rename from app/src/main/java/com/owncloud/android/ui/adapter/OCFileListHeaderViewHolder.kt rename to app/src/main/java/com/owncloud/android/ui/adapter/filelist/header/OCFileListHeaderViewHolder.kt index c6c47fb5c589..dddff5d02f37 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListHeaderViewHolder.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/header/OCFileListHeaderViewHolder.kt @@ -1,11 +1,12 @@ /* * Nextcloud - Android Client * + * SPDX-FileCopyrightText: 2026 Alper Ozturk * SPDX-FileCopyrightText: 2022 Tobias Kaminsky * SPDX-FileCopyrightText: 2022 Nextcloud GmbH * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only */ -package com.owncloud.android.ui.adapter +package com.owncloud.android.ui.adapter.filelist.header import androidx.recyclerview.widget.RecyclerView import com.owncloud.android.databinding.ListHeaderBinding diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/AvatarShareesProvider.kt b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/item/AvatarShareesProvider.kt similarity index 93% rename from app/src/main/java/com/owncloud/android/ui/adapter/filelist/AvatarShareesProvider.kt rename to app/src/main/java/com/owncloud/android/ui/adapter/filelist/item/AvatarShareesProvider.kt index 25c6d2718f4a..049220438f4a 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/AvatarShareesProvider.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/item/AvatarShareesProvider.kt @@ -5,7 +5,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -package com.owncloud.android.ui.adapter.filelist +package com.owncloud.android.ui.adapter.filelist.item import com.owncloud.android.datamodel.OCFile import com.owncloud.android.lib.resources.shares.ShareType diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/ListGridItemViewHolder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/item/ListGridItemViewHolder.kt similarity index 82% rename from app/src/main/java/com/owncloud/android/ui/adapter/ListGridItemViewHolder.kt rename to app/src/main/java/com/owncloud/android/ui/adapter/filelist/item/ListGridItemViewHolder.kt index f586ed12ef2f..60ca41a5ff88 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/ListGridItemViewHolder.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/item/ListGridItemViewHolder.kt @@ -1,11 +1,12 @@ /* * Nextcloud - Android Client * + * SPDX-FileCopyrightText: 2026 Alper Ozturk * SPDX-FileCopyrightText: 2022 Tobias Kaminsky * SPDX-FileCopyrightText: 2022 Nextcloud GmbH * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only */ -package com.owncloud.android.ui.adapter +package com.owncloud.android.ui.adapter.filelist.item import android.view.View import android.widget.TextView diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/ListItemViewHolder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/item/ListItemViewHolder.kt similarity index 87% rename from app/src/main/java/com/owncloud/android/ui/adapter/ListItemViewHolder.kt rename to app/src/main/java/com/owncloud/android/ui/adapter/filelist/item/ListItemViewHolder.kt index 6d969b6a9790..fbc855eed777 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/ListItemViewHolder.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/item/ListItemViewHolder.kt @@ -1,11 +1,12 @@ /* * Nextcloud - Android Client * + * SPDX-FileCopyrightText: 2026 Alper Ozturk * SPDX-FileCopyrightText: 2022 Tobias Kaminsky * SPDX-FileCopyrightText: 2022 Nextcloud GmbH * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only */ -package com.owncloud.android.ui.adapter +package com.owncloud.android.ui.adapter.filelist.item import android.view.View import android.widget.ImageView diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/ListViewHolder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/item/ListViewHolder.kt similarity index 88% rename from app/src/main/java/com/owncloud/android/ui/adapter/ListViewHolder.kt rename to app/src/main/java/com/owncloud/android/ui/adapter/filelist/item/ListViewHolder.kt index cceefd293dbf..7de0f03bbab2 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/ListViewHolder.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/item/ListViewHolder.kt @@ -1,11 +1,12 @@ /* * Nextcloud - Android Client * + * SPDX-FileCopyrightText: 2026 Alper Ozturk * SPDX-FileCopyrightText: 2022 Tobias Kaminsky * SPDX-FileCopyrightText: 2022 Nextcloud GmbH * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only */ -package com.owncloud.android.ui.adapter +package com.owncloud.android.ui.adapter.filelist.item import android.view.View import android.widget.ImageButton diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/item/OCFileListDelegate.kt similarity index 97% rename from app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt rename to app/src/main/java/com/owncloud/android/ui/adapter/filelist/item/OCFileListDelegate.kt index b1e713211dd0..65b0a4c2a6f0 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/item/OCFileListDelegate.kt @@ -1,11 +1,12 @@ /* * Nextcloud - Android Client * + * SPDX-FileCopyrightText: 2026 Alper Ozturk * SPDX-FileCopyrightText: 2022 Tobias Kaminsky * SPDX-FileCopyrightText: 2022 Nextcloud GmbH * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only */ -package com.owncloud.android.ui.adapter +package com.owncloud.android.ui.adapter.filelist.item import android.content.Context import android.view.View @@ -35,6 +36,8 @@ import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.ui.activity.AlbumsPickerActivity import com.owncloud.android.ui.activity.ComponentsGetter import com.owncloud.android.ui.activity.FolderPickerActivity +import com.owncloud.android.ui.adapter.GalleryRowHolder +import com.owncloud.android.ui.adapter.filelist.recommended.RecommendedFilesViewHolder import com.owncloud.android.ui.fragment.SearchType import com.owncloud.android.ui.fragment.albums.AlbumItemsFragment import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface @@ -280,7 +283,7 @@ class OCFileListDelegate( private fun setItemLayoutOnClickListeners(file: OCFile, gridViewHolder: ListViewHolder) { gridViewHolder.itemLayout.setOnClickListener { ocFileListFragmentInterface.onItemClicked(file) } - if (!hideItemOptions && gridViewHolder !is OCFileListRecommendedItemViewHolder) { + if (!hideItemOptions && gridViewHolder !is RecommendedFilesViewHolder) { gridViewHolder.itemLayout.apply { isLongClickable = true setOnLongClickListener { diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListGridItemViewHolder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/item/OCFileListGridItemViewHolder.kt similarity index 95% rename from app/src/main/java/com/owncloud/android/ui/adapter/OCFileListGridItemViewHolder.kt rename to app/src/main/java/com/owncloud/android/ui/adapter/filelist/item/OCFileListGridItemViewHolder.kt index 65aaeb970944..0dfbd55f9651 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListGridItemViewHolder.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/item/OCFileListGridItemViewHolder.kt @@ -1,11 +1,12 @@ /* * Nextcloud - Android Client * + * SPDX-FileCopyrightText: 2026 Alper Ozturk * SPDX-FileCopyrightText: 2022 Tobias Kaminsky * SPDX-FileCopyrightText: 2022 Nextcloud GmbH * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only */ -package com.owncloud.android.ui.adapter +package com.owncloud.android.ui.adapter.filelist.item import android.view.View import android.widget.ImageButton diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListItemBinder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/item/OCFileListItemBinder.kt similarity index 95% rename from app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListItemBinder.kt rename to app/src/main/java/com/owncloud/android/ui/adapter/filelist/item/OCFileListItemBinder.kt index a9469c1e3d45..b2cc94194a51 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListItemBinder.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/item/OCFileListItemBinder.kt @@ -4,7 +4,7 @@ * SPDX-FileCopyrightText: 2026 Alper Ozturk * SPDX-License-Identifier: AGPL-3.0-or-later */ -package com.owncloud.android.ui.adapter.filelist +package com.owncloud.android.ui.adapter.filelist.item import android.app.Activity import android.content.res.ColorStateList @@ -23,12 +23,6 @@ import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.datamodel.OCFile import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.lib.resources.tags.Tag -import com.owncloud.android.ui.adapter.ListGridItemViewHolder -import com.owncloud.android.ui.adapter.ListItemViewHolder -import com.owncloud.android.ui.adapter.ListViewHolder -import com.owncloud.android.ui.adapter.OCFileListGridItemViewHolder -import com.owncloud.android.ui.adapter.OCFileListItemViewHolder -import com.owncloud.android.ui.adapter.OCFileListViewHolder import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface import com.owncloud.android.utils.FileStorageUtils import com.owncloud.android.utils.theme.ViewThemeUtils diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListItemViewHolder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/item/OCFileListItemViewHolder.kt similarity index 96% rename from app/src/main/java/com/owncloud/android/ui/adapter/OCFileListItemViewHolder.kt rename to app/src/main/java/com/owncloud/android/ui/adapter/filelist/item/OCFileListItemViewHolder.kt index 0bc522560bf6..0bbd6804e9dd 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListItemViewHolder.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/item/OCFileListItemViewHolder.kt @@ -1,11 +1,12 @@ /* * Nextcloud - Android Client * + * SPDX-FileCopyrightText: 2026 Alper Ozturk * SPDX-FileCopyrightText: 2022 Tobias Kaminsky * SPDX-FileCopyrightText: 2022 Nextcloud GmbH * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only */ -package com.owncloud.android.ui.adapter +package com.owncloud.android.ui.adapter.filelist.item import android.view.View import android.widget.ImageButton diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListViewHolder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/item/OCFileListViewHolder.kt similarity index 95% rename from app/src/main/java/com/owncloud/android/ui/adapter/OCFileListViewHolder.kt rename to app/src/main/java/com/owncloud/android/ui/adapter/filelist/item/OCFileListViewHolder.kt index 1de965ae1083..d96845cfd6c8 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListViewHolder.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/item/OCFileListViewHolder.kt @@ -1,11 +1,12 @@ /* * Nextcloud - Android Client * + * SPDX-FileCopyrightText: 2026 Alper Ozturk * SPDX-FileCopyrightText: 2022 Tobias Kaminsky * SPDX-FileCopyrightText: 2022 Nextcloud GmbH * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only */ -package com.owncloud.android.ui.adapter +package com.owncloud.android.ui.adapter.filelist.item import android.view.View import android.widget.ImageButton diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/RecommendedFilesAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/recommended/RecommendedFilesAdapter.kt similarity index 73% rename from app/src/main/java/com/owncloud/android/ui/adapter/RecommendedFilesAdapter.kt rename to app/src/main/java/com/owncloud/android/ui/adapter/filelist/recommended/RecommendedFilesAdapter.kt index f00c483b28eb..fbd8fdadf37c 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/RecommendedFilesAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/recommended/RecommendedFilesAdapter.kt @@ -5,30 +5,31 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -package com.owncloud.android.ui.adapter +package com.owncloud.android.ui.adapter.filelist.recommended import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import com.owncloud.android.databinding.RecommendedFileItemBinding import com.owncloud.android.datamodel.OCFile +import com.owncloud.android.ui.adapter.filelist.OCFileListAdapter class RecommendedFilesAdapter( private val fileListAdapter: OCFileListAdapter, private val recommendations: List -) : RecyclerView.Adapter() { +) : RecyclerView.Adapter() { fun getItemPosition(file: OCFile): Int = recommendations.indexOf(file) - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): OCFileListRecommendedItemViewHolder { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecommendedFilesViewHolder { val binding = RecommendedFileItemBinding .inflate(LayoutInflater.from(parent.context), parent, false) - return OCFileListRecommendedItemViewHolder(binding) + return RecommendedFilesViewHolder(binding) } override fun getItemCount(): Int = recommendations.size - override fun onBindViewHolder(holder: OCFileListRecommendedItemViewHolder, position: Int) { + override fun onBindViewHolder(holder: RecommendedFilesViewHolder, position: Int) { val item = recommendations[position] fileListAdapter.bindRecommendedFilesHolder(holder, item) holder.reason.text = item.reason diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListRecommendedItemViewHolder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/recommended/RecommendedFilesViewHolder.kt similarity index 91% rename from app/src/main/java/com/owncloud/android/ui/adapter/OCFileListRecommendedItemViewHolder.kt rename to app/src/main/java/com/owncloud/android/ui/adapter/filelist/recommended/RecommendedFilesViewHolder.kt index 846d3a856b4f..920a714d3de8 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListRecommendedItemViewHolder.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/recommended/RecommendedFilesViewHolder.kt @@ -5,7 +5,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -package com.owncloud.android.ui.adapter +package com.owncloud.android.ui.adapter.filelist.recommended import android.view.View import android.widget.ImageButton @@ -16,8 +16,9 @@ import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import com.elyeproj.loaderviewlibrary.LoaderImageView import com.owncloud.android.databinding.RecommendedFileItemBinding +import com.owncloud.android.ui.adapter.filelist.item.ListGridItemViewHolder -class OCFileListRecommendedItemViewHolder(private val binding: RecommendedFileItemBinding) : +class RecommendedFilesViewHolder(private val binding: RecommendedFileItemBinding) : RecyclerView.ViewHolder(binding.root), ListGridItemViewHolder { diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.kt index 3febf194e283..c9734a3e1a6a 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.kt @@ -70,7 +70,7 @@ import com.owncloud.android.ui.activity.FolderPickerActivity import com.owncloud.android.ui.activity.OnEnforceableRefreshListener import com.owncloud.android.ui.activity.UploadFilesActivity import com.owncloud.android.ui.adapter.localFileList.LocalFileListAdapter -import com.owncloud.android.ui.adapter.OCFileListAdapter +import com.owncloud.android.ui.adapter.filelist.OCFileListAdapter import com.owncloud.android.ui.events.SearchEvent import com.owncloud.android.utils.theme.ViewThemeUtils import org.greenrobot.eventbus.EventBus diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileListLayoutManager.kt b/app/src/main/java/com/owncloud/android/ui/fragment/FileListLayoutManager.kt index d33d4cd797c0..027acdd4c272 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileListLayoutManager.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileListLayoutManager.kt @@ -16,7 +16,7 @@ import androidx.recyclerview.widget.RecyclerView import com.nextcloud.client.preferences.AppPreferences import com.nextcloud.ui.sort.SortOrderUi import com.owncloud.android.lib.common.utils.Log_OC -import com.owncloud.android.ui.adapter.OCFileListAdapter +import com.owncloud.android.ui.adapter.filelist.OCFileListAdapter import com.owncloud.android.utils.FileSortOrder class FileListLayoutManager(private val fragment: OCFileListFragment, private val preferences: AppPreferences) { diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/GalleryFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/GalleryFragment.kt index a98dd0afe9ff..9676cd66aed4 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/GalleryFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/GalleryFragment.kt @@ -51,7 +51,7 @@ import com.owncloud.android.ui.activity.AlbumsPickerActivity import com.owncloud.android.ui.activity.FileDisplayActivity import com.owncloud.android.ui.activity.FolderPickerActivity import com.owncloud.android.ui.activity.ToolbarActivity -import com.owncloud.android.ui.adapter.CommonOCFileListAdapterInterface +import com.owncloud.android.ui.adapter.filelist.CommonOCFileListAdapterInterface import com.owncloud.android.ui.adapter.GalleryAdapter import com.owncloud.android.ui.asynctasks.GallerySearchTask import com.owncloud.android.ui.events.ChangeMenuEvent diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java index 58389d12a2e5..cb38a8f7d60d 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -96,8 +96,8 @@ import com.owncloud.android.ui.activity.OnEnforceableRefreshListener; import com.owncloud.android.ui.activity.TextEditorWebView; import com.owncloud.android.ui.activity.UploadFilesActivity; -import com.owncloud.android.ui.adapter.CommonOCFileListAdapterInterface; -import com.owncloud.android.ui.adapter.OCFileListAdapter; +import com.owncloud.android.ui.adapter.filelist.CommonOCFileListAdapterInterface; +import com.owncloud.android.ui.adapter.filelist.OCFileListAdapter; import com.owncloud.android.ui.dialog.ChooseRichDocumentsTemplateDialogFragment; import com.owncloud.android.ui.dialog.ChooseTemplateDialogFragment; import com.owncloud.android.ui.dialog.ConfirmationDialogFragment; diff --git a/app/src/main/java/com/owncloud/android/ui/interfaces/OCFileListFragmentInterface.java b/app/src/main/java/com/owncloud/android/ui/interfaces/OCFileListFragmentInterface.java index c1a620aaddbb..77119296f829 100644 --- a/app/src/main/java/com/owncloud/android/ui/interfaces/OCFileListFragmentInterface.java +++ b/app/src/main/java/com/owncloud/android/ui/interfaces/OCFileListFragmentInterface.java @@ -10,7 +10,7 @@ import android.view.View; import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.ui.adapter.OCFileListAdapter; +import com.owncloud.android.ui.adapter.filelist.OCFileListAdapter; /** * Interface for communication between {@link com.owncloud.android.ui.fragment.OCFileListFragment} diff --git a/app/src/test/java/com/owncloud/android/ui/adapter/MockOCFileListAdapterDataProvider.kt b/app/src/test/java/com/owncloud/android/ui/adapter/filelist/data/MockOCFileListAdapterDataProvider.kt similarity index 96% rename from app/src/test/java/com/owncloud/android/ui/adapter/MockOCFileListAdapterDataProvider.kt rename to app/src/test/java/com/owncloud/android/ui/adapter/filelist/data/MockOCFileListAdapterDataProvider.kt index e6ba7cf0f122..19b3ed2aa5d8 100644 --- a/app/src/test/java/com/owncloud/android/ui/adapter/MockOCFileListAdapterDataProvider.kt +++ b/app/src/test/java/com/owncloud/android/ui/adapter/filelist/data/MockOCFileListAdapterDataProvider.kt @@ -5,11 +5,10 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -package com.owncloud.android.ui.adapter +package com.owncloud.android.ui.adapter.filelist.data import com.nextcloud.client.database.entity.FileEntity import com.owncloud.android.datamodel.OCFile -import com.owncloud.android.ui.adapter.filelist.OCFileListAdapterDataProvider import com.owncloud.android.utils.MimeType @Suppress("LongParameterList", "MagicNumber") diff --git a/app/src/test/java/com/owncloud/android/ui/adapter/OCFileListAdapterHelperTest.kt b/app/src/test/java/com/owncloud/android/ui/adapter/filelist/data/OCFileListAdapterHelperTest.kt similarity index 98% rename from app/src/test/java/com/owncloud/android/ui/adapter/OCFileListAdapterHelperTest.kt rename to app/src/test/java/com/owncloud/android/ui/adapter/filelist/data/OCFileListAdapterHelperTest.kt index 0b4252c5daf4..379d716a5a14 100644 --- a/app/src/test/java/com/owncloud/android/ui/adapter/OCFileListAdapterHelperTest.kt +++ b/app/src/test/java/com/owncloud/android/ui/adapter/filelist/data/OCFileListAdapterHelperTest.kt @@ -5,13 +5,12 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -package com.owncloud.android.ui.adapter +package com.owncloud.android.ui.adapter.filelist.data import android.content.Context import com.nextcloud.client.preferences.AppPreferences import com.owncloud.android.MainApp import com.owncloud.android.datamodel.OCFile -import com.owncloud.android.ui.adapter.filelist.OCFileListAdapterHelper import com.owncloud.android.utils.FileSortOrder import com.owncloud.android.utils.MimeType import io.mockk.every From 17fc711976f7a9a68938dd61f199c050693ad460 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 13 Oct 2026 10:52:23 +0200 Subject: [PATCH 6/8] use kt extension and simplify Signed-off-by: alperozturk96 --- .../android/ui/adapter/filelist/OCFileListAdapter.kt | 10 ++++------ .../ui/adapter/filelist/item/OCFileListItemBinder.kt | 7 ++++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListAdapter.kt index 3cb2a83e7f2a..281617ad29ac 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListAdapter.kt @@ -124,6 +124,8 @@ class OCFileListAdapter( transferServiceGetter.storageManager ?: FileDataStorageManager(user, activity.contentResolver) private var adapterDataProvider: OCFileListAdapterDataProvider = OCFileListAdapterDataProviderImpl(storageManager) private val capability: OCCapability = CapabilityUtils.getCapability(user, activity) + + @Suppress("DEPRECATION") private val userId: String? = AccountManager .get(activity) .getUserData(user.toPlatformAccount(), AccountUtils.Constants.KEY_USER_ID) @@ -380,13 +382,9 @@ class OCFileListAdapter( fun shouldShowHeader(): Boolean { val directory = currentDirectory - if (directory == null || MainApp.isOnlyOnDevice()) { - return false - } - - return shouldShowRecommendedFiles() || + return !(directory == null || MainApp.isOnlyOnDevice()) && (shouldShowRecommendedFiles() || headerBinder.shouldShowOpenInNotes(directory) || - headerBinder.hasRichWorkspace(directory) + headerBinder.hasRichWorkspace(directory)) } fun updateAdapter(newFiles: List, directory: OCFile?) { diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/item/OCFileListItemBinder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/item/OCFileListItemBinder.kt index b2cc94194a51..6aba9f710d3e 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/item/OCFileListItemBinder.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/item/OCFileListItemBinder.kt @@ -8,8 +8,9 @@ package com.owncloud.android.ui.adapter.filelist.item import android.app.Activity import android.content.res.ColorStateList -import android.graphics.Color import android.view.View +import androidx.core.graphics.toColorInt +import androidx.core.view.isNotEmpty import com.google.android.material.chip.Chip import com.nextcloud.client.account.User import com.nextcloud.model.OfflineOperationType @@ -109,7 +110,7 @@ internal class OCFileListItemBinder( if (!isShared || !showItemOptions) { sharedAvatars.boundFileId = null sharedAvatars.visibility = View.GONE - if (sharedAvatars.childCount > 0) { + if (sharedAvatars.isNotEmpty()) { sharedAvatars.removeAllViews() } return @@ -159,7 +160,7 @@ internal class OCFileListItemBinder( } try { - val color = Color.parseColor(tagColor) + val color = tagColor.toColorInt() chip.chipStrokeColor = ColorStateList.valueOf(color) chip.setTextColor(color) } catch (e: IllegalArgumentException) { From cec4525b6484537bd318900b47bc74acefcc4c37 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 13 Oct 2026 11:45:51 +0200 Subject: [PATCH 7/8] fix spotless check Signed-off-by: alperozturk96 --- .../android/ui/adapter/filelist/OCFileListAdapter.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListAdapter.kt index 281617ad29ac..ae7c42be7ee8 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListAdapter.kt @@ -382,9 +382,11 @@ class OCFileListAdapter( fun shouldShowHeader(): Boolean { val directory = currentDirectory - return !(directory == null || MainApp.isOnlyOnDevice()) && (shouldShowRecommendedFiles() || - headerBinder.shouldShowOpenInNotes(directory) || - headerBinder.hasRichWorkspace(directory)) + return !(directory == null || MainApp.isOnlyOnDevice()) && ( + shouldShowRecommendedFiles() || + headerBinder.shouldShowOpenInNotes(directory) || + headerBinder.hasRichWorkspace(directory) + ) } fun updateAdapter(newFiles: List, directory: OCFile?) { From 2ffd6b8c8f92e2a61cbb1ad8b7333931a69f3ea8 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 14 Oct 2026 09:14:03 +0200 Subject: [PATCH 8/8] add folder mark favorite action Signed-off-by: alperozturk96 --- .../android/ui/adapter/filelist/OCFileListAdapter.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListAdapter.kt index ae7c42be7ee8..ffc92edafda6 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListAdapter.kt @@ -66,8 +66,8 @@ import com.owncloud.android.ui.adapter.filelist.item.OCFileListDelegate import com.owncloud.android.ui.adapter.filelist.item.OCFileListGridItemViewHolder import com.owncloud.android.ui.adapter.filelist.item.OCFileListItemBinder import com.owncloud.android.ui.adapter.filelist.item.OCFileListItemViewHolder -import com.owncloud.android.ui.adapter.filelist.recommended.RecommendedFilesViewHolder import com.owncloud.android.ui.adapter.filelist.recommended.RecommendedFilesAdapter +import com.owncloud.android.ui.adapter.filelist.recommended.RecommendedFilesViewHolder import com.owncloud.android.ui.fragment.OCFileListFragment import com.owncloud.android.ui.fragment.SearchType import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface @@ -511,6 +511,11 @@ class OCFileListAdapter( } } + if (currentDirectory?.remotePath == remotePath) { + currentDirectory?.isFavorite = isFavorite + storageManager.saveFile(currentDirectory) + } + mainHandler.post { val sortedFiles = differ.sortForCurrentView( differ.latestFiles.toMutableList(),