diff --git a/app/src/main/java/com/nextcloud/client/di/AppModule.java b/app/src/main/java/com/nextcloud/client/di/AppModule.java
index 32a0150aa0f3..58dee51ee036 100644
--- a/app/src/main/java/com/nextcloud/client/di/AppModule.java
+++ b/app/src/main/java/com/nextcloud/client/di/AppModule.java
@@ -57,6 +57,7 @@
import com.owncloud.android.ui.activities.data.files.FilesServiceApiImpl;
import com.owncloud.android.ui.activities.data.files.RemoteFilesRepository;
import com.owncloud.android.ui.dialog.setupEncryption.CertificateValidator;
+import com.owncloud.android.utils.overlay.OverlayManager;
import com.owncloud.android.utils.theme.ViewThemeUtils;
import org.greenrobot.eventbus.EventBus;
@@ -268,4 +269,15 @@ UsersAndGroupsSearchConfig userAndGroupSearchConfig() {
CertificateValidator certificateValidator() {
return new CertificateValidator();
}
+
+ @Provides
+ @Singleton
+ OverlayManager overlayManager(
+ SyncedFolderProvider syncedFolderProvider,
+ AppPreferences appPreferences,
+ ViewThemeUtils viewThemeUtils,
+ Context context,
+ UserAccountManager accountManager) {
+ return new OverlayManager(syncedFolderProvider, appPreferences, viewThemeUtils, context, accountManager);
+ }
}
diff --git a/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt b/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt
index 28619ed1741f..6ebd22edf63e 100644
--- a/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt
+++ b/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt
@@ -46,6 +46,7 @@ import com.owncloud.android.ui.activity.ComponentsGetter
import com.owncloud.android.utils.DisplayUtils
import com.owncloud.android.utils.DisplayUtils.AvatarGenerationListener
import com.owncloud.android.utils.FileStorageUtils
+import com.owncloud.android.utils.overlay.OverlayManager
import com.owncloud.android.utils.theme.ViewThemeUtils
import javax.inject.Inject
@@ -68,6 +69,9 @@ class FileActionsBottomSheet :
@Inject
lateinit var syncedFolderProvider: SyncedFolderProvider
+ @Inject
+ lateinit var overlayManager: OverlayManager
+
private lateinit var viewModel: FileActionsViewModel
private var _binding: FileActionsBottomSheetBinding? = null
@@ -153,7 +157,7 @@ class FileActionsBottomSheet :
binding.thumbnailLayout.thumbnailShimmer,
syncedFolderProvider.preferences,
viewThemeUtils,
- syncedFolderProvider
+ overlayManager
)
}
}
diff --git a/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileActionsBottomSheet.kt b/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileActionsBottomSheet.kt
index 82422d9e64ee..89573ec5f443 100644
--- a/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileActionsBottomSheet.kt
+++ b/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileActionsBottomSheet.kt
@@ -35,6 +35,7 @@ import com.owncloud.android.datamodel.SyncedFolderProvider
import com.owncloud.android.datamodel.ThumbnailsCacheManager
import com.owncloud.android.lib.resources.trashbin.model.TrashbinFile
import com.owncloud.android.utils.DisplayUtils
+import com.owncloud.android.utils.overlay.OverlayManager
import com.owncloud.android.utils.theme.ViewThemeUtils
import javax.inject.Inject
@@ -57,6 +58,9 @@ class TrashbinFileActionsBottomSheet :
@Inject
lateinit var syncedFolderProvider: SyncedFolderProvider
+ @Inject
+ lateinit var overlayManager: OverlayManager
+
private lateinit var viewModel: TrashbinFileActionsViewModel
private var _binding: FileActionsBottomSheetBinding? = null
@@ -129,7 +133,7 @@ class TrashbinFileActionsBottomSheet :
binding.thumbnailLayout.thumbnailShimmer,
syncedFolderProvider.preferences,
viewThemeUtils,
- syncedFolderProvider
+ overlayManager
)
}
}
diff --git a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java
index 497776f6babf..c999706f9fe3 100644
--- a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java
+++ b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java
@@ -147,6 +147,24 @@ OCFile getFileByDecryptedRemotePath(String path) {
return getFileByPath(ProviderTableMeta.FILE_PATH_DECRYPTED, path);
}
+ /**
+ * Returns the {@link OCFile} for the given remote path.
+ * Tries the path as-is first; if not found, appends a trailing "/" for folders.
+ *
+ * @param path The file or folder path.
+ * @return The matching {@link OCFile}, or null if not found.
+ */
+ @Nullable
+ public OCFile getFileByRemotePath(String path) {
+ OCFile file = getFileByDecryptedRemotePath(path);
+
+ if (file == null) {
+ file = getFileByDecryptedRemotePath(path + OCFile.PATH_SEPARATOR);
+ }
+
+ return file;
+ }
+
public void addCreateFileOfflineOperation(String[] localPaths, String[] remotePaths) {
if (localPaths.length != remotePaths.length) {
Log_OC.d(TAG, "Local path and remote path size do not match");
diff --git a/app/src/main/java/com/owncloud/android/datamodel/SyncedFolderProvider.java b/app/src/main/java/com/owncloud/android/datamodel/SyncedFolderProvider.java
index ff4c00cfb269..a2fb5cb0afb4 100644
--- a/app/src/main/java/com/owncloud/android/datamodel/SyncedFolderProvider.java
+++ b/app/src/main/java/com/owncloud/android/datamodel/SyncedFolderProvider.java
@@ -432,6 +432,10 @@ private ContentValues createContentValuesFromSyncedFolder(SyncedFolder syncedFol
* @return true if exist, false otherwise
*/
public boolean findByRemotePathAndAccount(String remotePath, User user) {
+ if (user == null) {
+ return false;
+ }
+
boolean result = false;
//if path ends with / then remove the last / to work the query right way
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java
index c93624ba352c..63b5d6951c13 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java
@@ -67,6 +67,7 @@
import com.owncloud.android.utils.FileSortOrder;
import com.owncloud.android.utils.FileStorageUtils;
import com.owncloud.android.utils.MimeTypeUtil;
+import com.owncloud.android.utils.overlay.OverlayManager;
import com.owncloud.android.utils.theme.CapabilityUtils;
import com.owncloud.android.utils.theme.ViewThemeUtils;
@@ -134,6 +135,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter recommendedFiles = new ArrayList<>();
private RecommendedFilesAdapter recommendedFilesAdapter;
private final OCFileListAdapterHelper helper = new OCFileListAdapterHelper();
+ private final OverlayManager overlayManager;
public OCFileListAdapter(
Activity activity,
@@ -144,7 +146,9 @@ public OCFileListAdapter(
OCFileListFragmentInterface ocFileListFragmentInterface,
boolean argHideItemOptions,
boolean gridView,
- final ViewThemeUtils viewThemeUtils) {
+ final ViewThemeUtils viewThemeUtils,
+ OverlayManager overlayManager) {
+ this.overlayManager = overlayManager;
this.ocFileListFragmentInterface = ocFileListFragmentInterface;
this.activity = activity;
this.preferences = preferences;
@@ -476,7 +480,7 @@ public void bindRecommendedFilesHolder(OCFileListRecommendedItemViewHolder holde
}
private void bindHolder(@NonNull RecyclerView.ViewHolder holder, ListViewHolder viewHolder, OCFile file) {
- ocFileListDelegate.bindViewHolder(viewHolder, file, currentDirectory, searchType);
+ ocFileListDelegate.bindViewHolder(viewHolder, file, currentDirectory, searchType, overlayManager);
if (holder instanceof ListItemViewHolder itemViewHolder) {
bindListItemViewHolder(itemViewHolder, file);
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt
index 343659e8938a..9e2d812282cb 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt
@@ -37,6 +37,7 @@ import com.owncloud.android.ui.fragment.SearchType
import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface
import com.owncloud.android.utils.DisplayUtils
import com.owncloud.android.utils.EncryptionUtils
+import com.owncloud.android.utils.overlay.OverlayManager
import com.owncloud.android.utils.theme.ViewThemeUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -175,7 +176,12 @@ class OCFileListDelegate(
}
}
- fun setThumbnail(thumbnail: ImageView, shimmerThumbnail: LoaderImageView?, file: OCFile) {
+ fun setThumbnail(
+ thumbnail: ImageView,
+ shimmerThumbnail: LoaderImageView?,
+ file: OCFile,
+ overlayManager: OverlayManager
+ ) {
DisplayUtils.setThumbnail(
file,
thumbnail,
@@ -187,16 +193,22 @@ class OCFileListDelegate(
shimmerThumbnail,
preferences,
viewThemeUtils,
- syncFolderProvider
+ overlayManager
)
}
@Suppress("MagicNumber")
- fun bindViewHolder(viewHolder: ListViewHolder, file: OCFile, currentDirectory: OCFile?, searchType: SearchType?) {
+ fun bindViewHolder(
+ viewHolder: ListViewHolder,
+ file: OCFile,
+ currentDirectory: OCFile?,
+ searchType: SearchType?,
+ overlayManager: OverlayManager
+ ) {
// thumbnail
viewHolder.imageFileName?.text = file.fileName
viewHolder.thumbnail.tag = file.fileId
- setThumbnail(viewHolder.thumbnail, viewHolder.shimmerThumbnail, file)
+ setThumbnail(viewHolder.thumbnail, viewHolder.shimmerThumbnail, file, overlayManager)
// item layout + click listeners
bindGridItemLayout(file, viewHolder)
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchCurrentDirItemViewHolder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchCurrentDirItemViewHolder.kt
index a355cd5d4d0b..d34f33c80b08 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchCurrentDirItemViewHolder.kt
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchCurrentDirItemViewHolder.kt
@@ -20,6 +20,7 @@ import com.owncloud.android.datamodel.SyncedFolderProvider
import com.owncloud.android.ui.interfaces.UnifiedSearchCurrentDirItemAction
import com.owncloud.android.utils.DisplayUtils
import com.owncloud.android.utils.FileStorageUtils
+import com.owncloud.android.utils.overlay.OverlayManager
import com.owncloud.android.utils.theme.ViewThemeUtils
@Suppress("LongParameterList")
@@ -31,8 +32,8 @@ class UnifiedSearchCurrentDirItemViewHolder(
private val isRTL: Boolean,
private val user: User,
private val appPreferences: AppPreferences,
- private val syncedFolderProvider: SyncedFolderProvider,
- private val action: UnifiedSearchCurrentDirItemAction
+ private val action: UnifiedSearchCurrentDirItemAction,
+ private val overlayManager: OverlayManager
) : SectionedViewHolder(binding.unifiedSearchCurrentDirItemLayout) {
fun bind(file: OCFile) {
@@ -49,7 +50,6 @@ class UnifiedSearchCurrentDirItemViewHolder(
binding.filename.text = filename
}
- viewThemeUtils.platform.colorImageView(binding.thumbnail, ColorRole.PRIMARY)
DisplayUtils.setThumbnail(
file,
binding.thumbnail,
@@ -61,7 +61,7 @@ class UnifiedSearchCurrentDirItemViewHolder(
binding.thumbnailShimmer,
appPreferences,
viewThemeUtils,
- syncedFolderProvider
+ overlayManager
)
binding.more.setOnClickListener {
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchItemViewHolder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchItemViewHolder.kt
index fcc8f4661f39..b83e9de200b6 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchItemViewHolder.kt
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchItemViewHolder.kt
@@ -17,10 +17,16 @@ import com.nextcloud.utils.CalendarEventManager
import com.nextcloud.utils.ContactManager
import com.nextcloud.utils.GlideHelper
import com.nextcloud.utils.extensions.getType
+import com.nextcloud.utils.extensions.setVisibleIf
+import com.owncloud.android.R
import com.owncloud.android.databinding.UnifiedSearchItemBinding
import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.lib.common.SearchResultEntry
+import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.ui.interfaces.UnifiedSearchListInterface
+import com.owncloud.android.utils.DisplayUtils
+import com.owncloud.android.utils.MimeTypeUtil
+import com.owncloud.android.utils.overlay.OverlayManager
import com.owncloud.android.utils.theme.ViewThemeUtils
@Suppress("LongParameterList")
@@ -31,12 +37,13 @@ class UnifiedSearchItemViewHolder(
private val listInterface: UnifiedSearchListInterface,
private val filesAction: FilesAction,
val context: Context,
- private val nextcloudClient: NextcloudClient,
- private val viewThemeUtils: ViewThemeUtils
+ private val viewThemeUtils: ViewThemeUtils,
+ private val overlayManager: OverlayManager
) : SectionedViewHolder(binding.root) {
interface FilesAction {
fun showFilesAction(searchResultEntry: SearchResultEntry)
+ fun loadFileThumbnail(searchResultEntry: SearchResultEntry, onClientReady: (NextcloudClient) -> Unit)
}
private val contactManager = ContactManager(context)
@@ -44,25 +51,77 @@ class UnifiedSearchItemViewHolder(
fun bind(entry: SearchResultEntry) {
binding.title.text = entry.title
- binding.subline.text = entry.subline
+ bindSubline(entry)
+ bindLocalFileIndicator(entry)
- if (entry.isFile && storageManager.getFileByDecryptedRemotePath(entry.remotePath()) != null) {
- binding.localFileIndicator.visibility = View.VISIBLE
+ val entryType = entry.getType()
+ bindThumbnail(entry, entryType)
+ bindMoreButton(entry)
+ binding.unifiedSearchItemLayout.setOnClickListener {
+ searchEntryOnClick(entry, entryType)
+ }
+ }
+
+ private fun bindSubline(entry: SearchResultEntry) {
+ if (entry.subline.isNotBlank()) {
+ binding.subline.visibility = View.VISIBLE
+ binding.subline.text = entry.subline
} else {
- binding.localFileIndicator.visibility = View.GONE
+ binding.subline.visibility = View.GONE
+
+ val paddingInDp = context.resources.getDimension(R.dimen.standard_padding)
+ val paddingInPx = DisplayUtils.convertDpToPixel(paddingInDp, context)
+ binding.titleContainer.setPadding(0, paddingInPx, 0, 0)
}
+ }
- val entryType = entry.getType()
- viewThemeUtils.platform.colorImageView(binding.thumbnail, ColorRole.PRIMARY)
- GlideHelper.loadIntoImageView(
- context,
- nextcloudClient,
- entry.thumbnailUrl,
- binding.thumbnail,
- entryType.iconId(),
- circleCrop = entry.rounded
- )
+ private fun bindLocalFileIndicator(entry: SearchResultEntry) {
+ val showLocalFileIndicator =
+ (entry.isFile && storageManager.getFileByDecryptedRemotePath(entry.remotePath()) != null)
+ binding.localFileIndicator.setVisibleIf(showLocalFileIndicator)
+ }
+
+ private fun bindThumbnail(entry: SearchResultEntry, entryType: SearchResultEntryType) {
+ val file = storageManager.getFileByRemotePath(entry.remotePath())
+
+ if (file?.isFolder == true) {
+ Log_OC.d("DEBUG FOLDER", "Path: ${entry.remotePath()}, isFolder=${file.isFolder}, " +
+ "mime=${file?.mimeType}")
+ viewThemeUtils.platform.colorImageView(binding.thumbnail, ColorRole.PRIMARY)
+ overlayManager.setFolderOverlayIcon(file, binding.thumbnailOverlayIcon)
+ } else {
+ binding.thumbnail.clearColorFilter()
+
+ if (file != null) {
+ Log_OC.d("DEBUG FILE", "Path: ${entry.remotePath()}, isFolder=${file.isFolder}, " +
+ "mime=${file?.mimeType}")
+
+ val icon = MimeTypeUtil.getFileTypeIcon(
+ file.mimeType,
+ file.fileName,
+ context,
+ viewThemeUtils
+ )
+ binding.thumbnail.setImageDrawable(icon)
+ } else if (entry.thumbnailUrl.isNotBlank()) {
+ Log_OC.d("DEBUG GLIDE", "URL: " + entry.thumbnailUrl)
+
+ filesAction.loadFileThumbnail(entry) { client ->
+ GlideHelper.loadIntoImageView(
+ context,
+ client,
+ entry.thumbnailUrl,
+ binding.thumbnail,
+ entryType.iconId(),
+ circleCrop = entry.rounded
+ )
+ }
+ } // FIXME: Settings, Apps category
+ }
+ }
+
+ private fun bindMoreButton(entry: SearchResultEntry) {
if (entry.isFile) {
binding.more.visibility = View.VISIBLE
binding.more.setOnClickListener {
@@ -71,10 +130,6 @@ class UnifiedSearchItemViewHolder(
} else {
binding.more.visibility = View.GONE
}
-
- binding.unifiedSearchItemLayout.setOnClickListener {
- searchEntryOnClick(entry, entryType)
- }
}
private fun searchEntryOnClick(entry: SearchResultEntry, entryType: SearchResultEntryType) {
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchListAdapter.kt
index da62218ff348..9b97dc82a219 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchListAdapter.kt
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchListAdapter.kt
@@ -17,7 +17,6 @@ import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter
import com.afollestad.sectionedrecyclerview.SectionedViewHolder
import com.nextcloud.client.account.User
import com.nextcloud.client.preferences.AppPreferences
-import com.nextcloud.common.NextcloudClient
import com.owncloud.android.R
import com.owncloud.android.databinding.UnifiedSearchCurrentDirectoryItemBinding
import com.owncloud.android.databinding.UnifiedSearchEmptyBinding
@@ -32,12 +31,13 @@ import com.owncloud.android.ui.interfaces.UnifiedSearchCurrentDirItemAction
import com.owncloud.android.ui.interfaces.UnifiedSearchListInterface
import com.owncloud.android.ui.unifiedsearch.UnifiedSearchSection
import com.owncloud.android.utils.DisplayUtils
+import com.owncloud.android.utils.overlay.OverlayManager
import com.owncloud.android.utils.theme.ViewThemeUtils
/**
* This Adapter populates a SectionedRecyclerView with search results by unified search
*/
-@Suppress("LongParameterList")
+@Suppress("LongParameterList", "LongMethod")
class UnifiedSearchListAdapter(
private val supportsOpeningCalendarContactsLocally: Boolean,
private val storageManager: FileDataStorageManager,
@@ -48,8 +48,8 @@ class UnifiedSearchListAdapter(
private val viewThemeUtils: ViewThemeUtils,
private val appPreferences: AppPreferences,
private val syncedFolderProvider: SyncedFolderProvider,
- private val nextcloudClient: NextcloudClient,
- private val currentDirItemAction: UnifiedSearchCurrentDirItemAction
+ private val currentDirItemAction: UnifiedSearchCurrentDirItemAction,
+ private val overlayManager: OverlayManager
) : SectionedRecyclerViewAdapter() {
companion object {
private const val VIEW_TYPE_EMPTY = Int.MAX_VALUE
@@ -93,8 +93,8 @@ class UnifiedSearchListAdapter(
listInterface,
filesAction,
context,
- nextcloudClient,
- viewThemeUtils
+ viewThemeUtils,
+ overlayManager
)
}
@@ -109,8 +109,8 @@ class UnifiedSearchListAdapter(
isRTL,
user,
appPreferences,
- syncedFolderProvider,
- currentDirItemAction
+ currentDirItemAction,
+ overlayManager
)
}
diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/ConflictsResolveDialog.kt b/app/src/main/java/com/owncloud/android/ui/dialog/ConflictsResolveDialog.kt
index 522626227391..db515bb7aa30 100644
--- a/app/src/main/java/com/owncloud/android/ui/dialog/ConflictsResolveDialog.kt
+++ b/app/src/main/java/com/owncloud/android/ui/dialog/ConflictsResolveDialog.kt
@@ -33,6 +33,7 @@ import com.owncloud.android.ui.dialog.parcel.ConflictDialogData
import com.owncloud.android.ui.dialog.parcel.ConflictFileData
import com.owncloud.android.utils.DisplayUtils
import com.owncloud.android.utils.MimeTypeUtil
+import com.owncloud.android.utils.overlay.OverlayManager
import com.owncloud.android.utils.theme.ViewThemeUtils
import java.io.File
import javax.inject.Inject
@@ -63,6 +64,9 @@ class ConflictsResolveDialog :
@Inject
lateinit var fileDataStorageManager: FileDataStorageManager
+ @Inject
+ lateinit var overlayManager: OverlayManager
+
enum class Decision {
CANCEL,
KEEP_BOTH,
@@ -232,7 +236,7 @@ class ConflictsResolveDialog :
null,
syncedFolderProvider.preferences,
viewThemeUtils,
- syncedFolderProvider
+ overlayManager
)
}
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 828aca199a55..e19b355e283e 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
@@ -114,6 +114,7 @@
import com.owncloud.android.utils.FileSortOrder;
import com.owncloud.android.utils.FileStorageUtils;
import com.owncloud.android.utils.PermissionUtil;
+import com.owncloud.android.utils.overlay.OverlayManager;
import com.owncloud.android.utils.theme.ThemeUtils;
import org.apache.commons.httpclient.HttpStatus;
@@ -206,6 +207,7 @@ public class OCFileListFragment extends ExtendedListFragment implements
@Inject ShortcutUtil shortcutUtil;
@Inject SyncedFolderProvider syncedFolderProvider;
@Inject AppScanOptionalFeature appScanOptionalFeature;
+ @Inject OverlayManager overlayManager;
protected FileFragment.ContainerActivity mContainerActivity;
@@ -461,7 +463,8 @@ protected void setAdapter(Bundle args) {
this,
hideItemOptions,
isGridViewPreferred(mFile),
- viewThemeUtils
+ viewThemeUtils,
+ overlayManager
);
setRecyclerViewAdapter(mAdapter);
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/UnifiedSearchFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/UnifiedSearchFragment.kt
index 7c8bd588807f..4f94423d584b 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/UnifiedSearchFragment.kt
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/UnifiedSearchFragment.kt
@@ -36,6 +36,7 @@ import com.nextcloud.client.di.Injectable
import com.nextcloud.client.di.ViewModelFactory
import com.nextcloud.client.network.ClientFactory
import com.nextcloud.client.preferences.AppPreferences
+import com.nextcloud.common.NextcloudClient
import com.nextcloud.utils.extensions.getTypedActivity
import com.nextcloud.utils.extensions.searchFilesByName
import com.nextcloud.utils.extensions.setVisibleIf
@@ -62,6 +63,7 @@ import com.owncloud.android.ui.unifiedsearch.UnifiedSearchViewModel
import com.owncloud.android.ui.unifiedsearch.filterOutHiddenFiles
import com.owncloud.android.utils.DisplayUtils
import com.owncloud.android.utils.PermissionUtil
+import com.owncloud.android.utils.overlay.OverlayManager
import com.owncloud.android.utils.theme.ViewThemeUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -106,6 +108,9 @@ class UnifiedSearchFragment :
}
}
+ @Inject
+ lateinit var overlayManager: OverlayManager
+
@Inject
lateinit var vmFactory: ViewModelFactory
@@ -133,6 +138,8 @@ class UnifiedSearchFragment :
@Inject
lateinit var clock: Clock
+ @Volatile private var client: NextcloudClient? = null
+
private var listOfHiddenFiles = ArrayList()
private var showMoreActions = false
private var currentDir: OCFile? = null
@@ -371,37 +378,30 @@ class UnifiedSearchFragment :
val syncedFolderProvider = SyncedFolderProvider(requireContext().contentResolver, appPreferences, clock)
val gridLayoutManager = GridLayoutManager(requireContext(), 1)
- lifecycleScope.launch(Dispatchers.IO) {
- val client =
- getTypedActivity(FileActivity::class.java)?.clientRepository?.getNextcloudClient() ?: return@launch
+ adapter = UnifiedSearchListAdapter(
+ supportsOpeningCalendarContactsLocally(),
+ storageManager,
+ this@UnifiedSearchFragment,
+ this@UnifiedSearchFragment,
+ currentAccountProvider.user,
+ requireContext(),
+ viewThemeUtils,
+ appPreferences,
+ syncedFolderProvider,
+ this@UnifiedSearchFragment,
+ overlayManager
+ )
+
+ adapter.shouldShowFooters(true)
+ adapter.setLayoutManager(gridLayoutManager)
+ binding.listRoot.layoutManager = gridLayoutManager
+ binding.listRoot.adapter = adapter
+ searchInCurrentDirectory(initialQuery ?: "")
- withContext(Dispatchers.Main) {
- adapter = UnifiedSearchListAdapter(
- supportsOpeningCalendarContactsLocally(),
- storageManager,
- this@UnifiedSearchFragment,
- this@UnifiedSearchFragment,
- currentAccountProvider.user,
- requireContext(),
- viewThemeUtils,
- appPreferences,
- syncedFolderProvider,
- client,
- this@UnifiedSearchFragment
- )
-
- adapter.shouldShowFooters(true)
- adapter.setLayoutManager(gridLayoutManager)
- binding.listRoot.layoutManager = gridLayoutManager
- binding.listRoot.adapter = adapter
- searchInCurrentDirectory(initialQuery ?: "")
-
- setUpViewModel()
- if (!initialQuery.isNullOrEmpty()) {
- vm.setQuery(initialQuery!!)
- vm.initialQuery()
- }
- }
+ setUpViewModel()
+ if (!initialQuery.isNullOrEmpty()) {
+ vm.setQuery(initialQuery!!)
+ vm.initialQuery()
}
}
@@ -466,6 +466,26 @@ class UnifiedSearchFragment :
vm.openResult(searchResultEntry)
}
+ override fun loadFileThumbnail(searchResultEntry: SearchResultEntry, onClientReady: (NextcloudClient) -> Unit) {
+ client?.let {
+ onClientReady(it)
+ return
+ }
+
+ lifecycleScope.launch(Dispatchers.IO) {
+ val newClient = getTypedActivity(FileActivity::class.java)
+ ?.clientRepository
+ ?.getNextcloudClient()
+ ?: return@launch
+
+ client = newClient
+
+ withContext(Dispatchers.Main) {
+ onClientReady(newClient)
+ }
+ }
+ }
+
override fun openFile(remotePath: String, showMoreActions: Boolean) {
this.showMoreActions = showMoreActions
vm.getRemoteFile(remotePath)
diff --git a/app/src/main/java/com/owncloud/android/utils/DisplayUtils.java b/app/src/main/java/com/owncloud/android/utils/DisplayUtils.java
index 45a2a2831ee8..7848e262e19e 100644
--- a/app/src/main/java/com/owncloud/android/utils/DisplayUtils.java
+++ b/app/src/main/java/com/owncloud/android/utils/DisplayUtils.java
@@ -57,13 +57,13 @@
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.datamodel.SyncedFolderProvider;
import com.owncloud.android.datamodel.ThumbnailsCacheManager;
import com.owncloud.android.lib.common.OwnCloudAccount;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.files.model.ServerFileInterface;
import com.owncloud.android.ui.TextDrawable;
import com.owncloud.android.ui.dialog.SortingOrderDialogFragment;
+import com.owncloud.android.utils.overlay.OverlayManager;
import com.owncloud.android.utils.theme.ViewThemeUtils;
import java.io.BufferedReader;
@@ -80,10 +80,8 @@
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
-import java.util.HashMap;
import java.util.List;
import java.util.Locale;
-import java.util.Map;
import java.util.TimeZone;
import androidx.annotation.NonNull;
@@ -111,12 +109,10 @@ public final class DisplayUtils {
private static final String[] sizeSuffixes = {"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"};
private static final int[] sizeScales = {0, 0, 1, 1, 1, 2, 2, 2, 2};
- private static final String MIME_TYPE_UNKNOWN = "Unknown type";
private static final String HTTP_PROTOCOL = "http://";
private static final String HTTPS_PROTOCOL = "https://";
private static final String TWITTER_HANDLE_PREFIX = "@";
- private static final int MIMETYPE_PARTS_COUNT = 2;
private static final int BYTE_SIZE_DIVIDER = 1024;
private static final double BYTE_SIZE_DIVIDER_DOUBLE = 1024.0;
private static final int DATE_TIME_PARTS_SIZE = 2;
@@ -124,24 +120,6 @@ public final class DisplayUtils {
public static final String MONTH_YEAR_PATTERN = "MMMM yyyy";
public static final String MONTH_PATTERN = "MMMM";
public static final String YEAR_PATTERN = "yyyy";
- public static final int SVG_SIZE = 512;
-
- private static Map mimeType2HumanReadable;
-
- static {
- mimeType2HumanReadable = new HashMap<>();
- // images
- mimeType2HumanReadable.put("image/jpeg", "JPEG image");
- mimeType2HumanReadable.put("image/jpg", "JPEG image");
- mimeType2HumanReadable.put("image/png", "PNG image");
- mimeType2HumanReadable.put("image/bmp", "Bitmap image");
- mimeType2HumanReadable.put("image/gif", "GIF image");
- mimeType2HumanReadable.put("image/svg+xml", "JPEG image");
- mimeType2HumanReadable.put("image/tiff", "TIFF image");
- // music
- mimeType2HumanReadable.put("audio/mpeg", "MP3 music file");
- mimeType2HumanReadable.put("application/ogg", "OGG music file");
- }
private DisplayUtils() {
// utility class -> private constructor
@@ -174,24 +152,6 @@ public static String bytesToHumanReadable(long bytes) {
}
}
- /**
- * Converts MIME types like "image/jpg" to more end user friendly output
- * like "JPG image".
- *
- * @param mimetype MIME type to convert
- * @return A human friendly version of the MIME type, {@link #MIME_TYPE_UNKNOWN} if it can't be converted
- */
- public static String convertMIMEtoPrettyPrint(String mimetype) {
- final String humanReadableMime = mimeType2HumanReadable.get(mimetype);
- if (humanReadableMime != null) {
- return humanReadableMime;
- }
- if (mimetype.split("/").length >= MIMETYPE_PARTS_COUNT) {
- return mimetype.split("/")[1].toUpperCase(Locale.getDefault()) + " file";
- }
- return MIME_TYPE_UNKNOWN;
- }
-
/**
* Converts Unix time to human readable format
*
@@ -356,21 +316,6 @@ public static CharSequence getRelativeDateTimeString(Context c,
}
}
- /**
- * Update the passed path removing the last "/" if it is not the root folder.
- *
- * @param path the path to be trimmed
- */
- public static String getPathWithoutLastSlash(String path) {
-
- // Remove last slash from path
- if (path.length() > 1 && path.charAt(path.length() - 1) == OCFile.PATH_SEPARATOR.charAt(0)) {
- return path.substring(0, path.length() - 1);
- }
-
- return path;
- }
-
/**
* Gets the screen size in pixels.
*
@@ -843,7 +788,7 @@ public static void setThumbnail(OCFile file,
LoaderImageView shimmerThumbnail,
AppPreferences preferences,
ViewThemeUtils viewThemeUtils,
- SyncedFolderProvider syncedFolderProvider) {
+ OverlayManager overlayManager) {
if (file == null || thumbnailView == null || context == null) {
return;
}
@@ -854,7 +799,7 @@ public static void setThumbnail(OCFile file,
}
if (file.isFolder()) {
- setThumbnailForFolder(file, thumbnailView, shimmerThumbnail, user, syncedFolderProvider, preferences, context, viewThemeUtils);
+ overlayManager.setFolderThumbnail(file, thumbnailView, shimmerThumbnail);
return;
}
@@ -899,17 +844,6 @@ private static void setThumbnailForOfflineOperation(OCFile file, ImageView thumb
}
}
- private static void setThumbnailForFolder(OCFile file, ImageView thumbnailView, LoaderImageView shimmerThumbnail, User user, SyncedFolderProvider syncedFolderProvider, AppPreferences preferences, Context context, ViewThemeUtils viewThemeUtils) {
- stopShimmer(shimmerThumbnail, thumbnailView);
-
- boolean isAutoUploadFolder = SyncedFolderProvider.isAutoUploadFolder(syncedFolderProvider, file, user);
- boolean isDarkModeActive = preferences.isDarkModeEnabled();
-
- final var overlayIconId = file.getFileOverlayIconId(isAutoUploadFolder);
- final var fileIcon = MimeTypeUtil.getFolderIcon(isDarkModeActive, overlayIconId, context, viewThemeUtils);
- thumbnailView.setImageDrawable(fileIcon);
- }
-
private static void setThumbnailFromCache(OCFile file, ImageView thumbnailView, FileDataStorageManager storageManager, List asyncTasks, boolean gridView, LoaderImageView shimmerThumbnail, User user, AppPreferences preferences, Context context, ViewThemeUtils viewThemeUtils) {
final var thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(ThumbnailsCacheManager.PREFIX_THUMBNAIL + file.getRemoteId());
if (thumbnail == null || file.isUpdateThumbnailNeeded()) {
diff --git a/app/src/main/java/com/owncloud/android/utils/overlay/OverlayManager.kt b/app/src/main/java/com/owncloud/android/utils/overlay/OverlayManager.kt
new file mode 100644
index 000000000000..4005a3435828
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/utils/overlay/OverlayManager.kt
@@ -0,0 +1,103 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2026 Alper Ozturk
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+package com.owncloud.android.utils.overlay
+
+import android.content.Context
+import android.view.View
+import android.widget.ImageView
+import androidx.core.content.ContextCompat
+import com.elyeproj.loaderviewlibrary.LoaderImageView
+import com.nextcloud.client.account.UserAccountManager
+import com.nextcloud.client.di.Injectable
+import com.nextcloud.client.preferences.AppPreferences
+import com.owncloud.android.datamodel.OCFile
+import com.owncloud.android.datamodel.SyncedFolderProvider
+import com.owncloud.android.utils.DisplayUtils
+import com.owncloud.android.utils.MimeTypeUtil
+import com.owncloud.android.utils.theme.ViewThemeUtils
+import javax.inject.Inject
+
+class OverlayManager @Inject constructor(
+ private val syncedFolderProvider: SyncedFolderProvider,
+ private val preferences: AppPreferences,
+ private val viewThemeUtils: ViewThemeUtils,
+ private val context: Context,
+ private val accountManager: UserAccountManager
+) : Injectable {
+
+ /**
+ * Sets the overlay icon for a folder into the provided [ImageView].
+ *
+ * The icon is only applied when:
+ * - The [folder] is not null
+ * - The [folder] represents a directory
+ * - A valid overlay icon resource can be resolved
+ *
+ * The overlay icon depends on whether the folder is configured
+ * as an auto-upload folder for the current user.
+ *
+ * @param folder The [OCFile] representing the folder.
+ * @param imageView The [ImageView] where the overlay icon will be displayed.
+ */
+ fun setFolderOverlayIcon(folder: OCFile?, imageView: ImageView) {
+ val overlayIconId = folder
+ ?.takeIf { it.isFolder }
+ ?.let { currentFolder ->
+ val isAutoUploadFolder = SyncedFolderProvider.isAutoUploadFolder(
+ syncedFolderProvider,
+ currentFolder,
+ accountManager.user
+ )
+ currentFolder.getFileOverlayIconId(isAutoUploadFolder)
+ }
+
+ if (overlayIconId == null) {
+ imageView.visibility = View.GONE
+ } else {
+ imageView.visibility = View.VISIBLE
+ imageView.setImageDrawable(ContextCompat.getDrawable(context, overlayIconId))
+ }
+ }
+
+ /**
+ * Sets the thumbnail for a folder into the provided [ImageView].
+ *
+ * This method:
+ * - Ensures the given [folder] is not null and represents a directory.
+ * - Stops any active shimmer/loading animation on [loaderImageView].
+ * - Resolves whether the folder is configured as an auto-upload folder
+ * for the current user.
+ * - Detects whether dark mode is currently enabled.
+ * - Retrieves the appropriate folder icon and overlay.
+ *
+ * The final drawable is created via `MimeTypeUtil.getFolderIcon(...)`,
+ * which returns a LayerDrawable. This drawable is built programmatically
+ * by stacking multiple layers (e.g., base folder icon + optional overlay icon)
+ * on top of each other, so everything is rendered inside a single [ImageView].
+ *
+ * @param folder The [OCFile] representing the folder.
+ * @param imageView The [ImageView] where the composed folder thumbnail
+ * will be displayed.
+ * @param loaderImageView Optional [LoaderImageView] used for shimmer/loading
+ * state handling. If provided, its shimmer animation will be stopped before
+ * applying the final icon.
+ */
+ fun setFolderThumbnail(folder: OCFile?, imageView: ImageView, loaderImageView: LoaderImageView?) {
+ if (folder == null || !folder.isFolder) return
+
+ DisplayUtils.stopShimmer(loaderImageView, imageView)
+
+ val isAutoUploadFolder =
+ SyncedFolderProvider.isAutoUploadFolder(syncedFolderProvider, folder, accountManager.user)
+ val isDarkModeActive = preferences.isDarkModeEnabled()
+
+ val overlayIconId = folder.getFileOverlayIconId(isAutoUploadFolder)
+ val icon = MimeTypeUtil.getFolderIcon(isDarkModeActive, overlayIconId, context, viewThemeUtils)
+ imageView.setImageDrawable(icon)
+ }
+}
diff --git a/app/src/main/res/layout/unified_search_item.xml b/app/src/main/res/layout/unified_search_item.xml
index 9f65a55268ca..ab8cf4038490 100755
--- a/app/src/main/res/layout/unified_search_item.xml
+++ b/app/src/main/res/layout/unified_search_item.xml
@@ -39,6 +39,18 @@
android:contentDescription="@null"
android:src="@drawable/folder" />
+
+