From a8cdaa70cc0ba0f869095caea2f812c09d3fbb68 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Wed, 11 Jun 2025 11:39:54 +0200 Subject: [PATCH 01/31] add createContentValueForRemoteFile Signed-off-by: alperozturk --- .../datamodel/FileDataStorageManager.java | 107 ++++++++++++++++-- .../operations/RefreshFolderOperation.java | 2 + 2 files changed, 97 insertions(+), 12 deletions(-) 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 a36acef5b5b9..ec3f4ad47ba8 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -1535,6 +1535,48 @@ public List getSharesByPathAndType(String path, ShareType type, String return shares; } + private ContentValues createContentValueForRemoteFile(RemoteFile remoteFile) { + ContentValues contentValues = new ContentValues(); + + // Aligned properties between RemoteFile and OCShare + contentValues.put(ProviderTableMeta.OCSHARES_PATH, remoteFile.getRemotePath()); + contentValues.put(ProviderTableMeta.OCSHARES_PERMISSIONS, remoteFile.getPermissions()); + contentValues.put(ProviderTableMeta.OCSHARES_IS_DIRECTORY, + "DIR".equals(remoteFile.getMimeType()) ? 1 : 0); + contentValues.put(ProviderTableMeta.OCSHARES_USER_ID, remoteFile.getOwnerId()); + contentValues.put(ProviderTableMeta.OCSHARES_NOTE, remoteFile.getNote()); + contentValues.put(ProviderTableMeta.OCSHARES_HIDE_DOWNLOAD, remoteFile.isHasPreview()); + + // Handle FileDownloadLimit - RemoteFile has a List while OCShare has a single object + List downloadLimits = remoteFile.getFileDownloadLimit(); + if (!downloadLimits.isEmpty()) { + FileDownloadLimit downloadLimit = downloadLimits.get(0); // Take the first one + contentValues.put(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_LIMIT, downloadLimit.getLimit()); + contentValues.put(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_COUNT, downloadLimit.getCount()); + } else { + contentValues.putNull(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_LIMIT); + contentValues.putNull(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_COUNT); + } + + // Set default/null values for non-matching OCShare fields + contentValues.putNull(ProviderTableMeta.OCSHARES_FILE_SOURCE); + contentValues.putNull(ProviderTableMeta.OCSHARES_ITEM_SOURCE); + contentValues.putNull(ProviderTableMeta.OCSHARES_SHARE_TYPE); + contentValues.putNull(ProviderTableMeta.OCSHARES_SHARE_WITH); + contentValues.putNull(ProviderTableMeta.OCSHARES_SHARED_DATE); + contentValues.putNull(ProviderTableMeta.OCSHARES_EXPIRATION_DATE); + contentValues.putNull(ProviderTableMeta.OCSHARES_TOKEN); + contentValues.putNull(ProviderTableMeta.OCSHARES_SHARE_WITH_DISPLAY_NAME); + contentValues.putNull(ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED); + contentValues.put(ProviderTableMeta.OCSHARES_ACCOUNT_OWNER, user.getAccountName()); // Assuming user is available + contentValues.put(ProviderTableMeta.OCSHARES_IS_PASSWORD_PROTECTED, 0); + contentValues.putNull(ProviderTableMeta.OCSHARES_SHARE_LINK); + contentValues.putNull(ProviderTableMeta.OCSHARES_SHARE_LABEL); + contentValues.putNull(ProviderTableMeta.OCSHARES_ATTRIBUTES); + + return contentValues; + } + private ContentValues createContentValueForShare(OCShare share) { ContentValues contentValues = new ContentValues(); contentValues.put(ProviderTableMeta.OCSHARES_FILE_SOURCE, share.getFileSource()); @@ -1743,6 +1785,29 @@ public void removeShare(OCShare share) { } } + public void saveSharesFromRemoteFile(List shares) { + final ArrayList operations = prepareInsertSharesFromRemoteFile(shares); + if (operations.isEmpty()) { + return; + } + applyBatch(operations); + } + + private ArrayList prepareInsertSharesFromRemoteFile(Iterable remoteFiles) { + final ArrayList operations = new ArrayList<>(); + + ContentValues contentValues; + for (RemoteFile remoteFile : remoteFiles) { + contentValues = createContentValueForRemoteFile(remoteFile); + operations.add(ContentProviderOperation + .newInsert(ProviderTableMeta.CONTENT_URI_SHARE) + .withValues(contentValues) + .build()); + } + + return operations; + } + public void saveSharesDB(List shares) { ArrayList operations = new ArrayList<>(); @@ -1759,20 +1824,39 @@ public void saveSharesDB(List shares) { // Add operations to insert shares operations = prepareInsertShares(shares, operations); + if (operations.isEmpty()) { + return; + } + // apply operations in batch - if (operations.size() > 0) { - Log_OC.d(TAG, String.format(Locale.ENGLISH, SENDING_TO_FILECONTENTPROVIDER_MSG, operations.size())); - try { - if (getContentResolver() != null) { - getContentResolver().applyBatch(MainApp.getAuthority(), operations); + Log_OC.d(TAG, String.format(Locale.ENGLISH, SENDING_TO_FILECONTENTPROVIDER_MSG, operations.size())); + applyBatch(operations); + } - } else { - getContentProviderClient().applyBatch(operations); - } + // TODO check: + private void resetShareFlags(List sharePaths) { + ArrayList operations = new ArrayList<>(); + String filePath = ""; + for (String sharePath : sharePaths) { + if (!filePath.equals(sharePath)) { + filePath = sharePath; + resetShareFlagInAFile(filePath); + prepareRemoveSharesInFile(filePath, operations); + } + } + } - } catch (OperationApplicationException | RemoteException e) { - Log_OC.e(TAG, EXCEPTION_MSG + e.getMessage(), e); + private void applyBatch(ArrayList operations) { + try { + if (getContentResolver() != null) { + getContentResolver().applyBatch(MainApp.getAuthority(), operations); + + } else { + getContentProviderClient().applyBatch(operations); } + + } catch (OperationApplicationException | RemoteException e) { + Log_OC.e(TAG, EXCEPTION_MSG + e.getMessage(), e); } } @@ -1830,8 +1914,7 @@ public void saveSharesInFolder(ArrayList shares, OCFile folder) { * @param operations List of operations * @return */ - private ArrayList prepareInsertShares( - Iterable shares, ArrayList operations) { + private ArrayList prepareInsertShares(Iterable shares, ArrayList operations) { ContentValues contentValues; // prepare operations to insert or update files to save in the given folder diff --git a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java index c49c10f36fec..1bb19123c776 100644 --- a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java @@ -794,6 +794,8 @@ private void startContentSynchronizations(List filesTo /** * Syncs the Share resources for the files contained in the folder refreshed (children, not deeper descendants). * + * + * fetch in the details only for one file * @param client Handler of a session with an OC server. */ private void refreshSharesForFolder(OwnCloudClient client) { From 4bbf559cb1f6bec68a528f3232d3e9d787f50900 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Wed, 11 Jun 2025 12:34:33 +0200 Subject: [PATCH 02/31] add createContentValueForRemoteFile Signed-off-by: alperozturk --- .../datamodel/FileDataStorageManager.java | 83 +++++++------------ .../providers/FileContentProvider.java | 8 +- 2 files changed, 39 insertions(+), 52 deletions(-) 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 ec3f4ad47ba8..2cb9ae8312e4 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -39,6 +39,7 @@ import com.nextcloud.client.database.dao.OfflineOperationDao; import com.nextcloud.client.database.entity.FileEntity; import com.nextcloud.client.database.entity.OfflineOperationEntity; +import com.nextcloud.client.database.entity.ShareEntity; import com.nextcloud.client.jobs.offlineOperations.repository.OfflineOperationsRepository; import com.nextcloud.client.jobs.offlineOperations.repository.OfflineOperationsRepositoryType; import com.nextcloud.model.OCFileFilterType; @@ -83,6 +84,7 @@ import java.util.List; import java.util.Locale; import java.util.Set; +import java.util.function.Consumer; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -1538,41 +1540,33 @@ public List getSharesByPathAndType(String path, ShareType type, String private ContentValues createContentValueForRemoteFile(RemoteFile remoteFile) { ContentValues contentValues = new ContentValues(); - // Aligned properties between RemoteFile and OCShare contentValues.put(ProviderTableMeta.OCSHARES_PATH, remoteFile.getRemotePath()); - contentValues.put(ProviderTableMeta.OCSHARES_PERMISSIONS, remoteFile.getPermissions()); - contentValues.put(ProviderTableMeta.OCSHARES_IS_DIRECTORY, - "DIR".equals(remoteFile.getMimeType()) ? 1 : 0); - contentValues.put(ProviderTableMeta.OCSHARES_USER_ID, remoteFile.getOwnerId()); - contentValues.put(ProviderTableMeta.OCSHARES_NOTE, remoteFile.getNote()); - contentValues.put(ProviderTableMeta.OCSHARES_HIDE_DOWNLOAD, remoteFile.isHasPreview()); - - // Handle FileDownloadLimit - RemoteFile has a List while OCShare has a single object - List downloadLimits = remoteFile.getFileDownloadLimit(); - if (!downloadLimits.isEmpty()) { - FileDownloadLimit downloadLimit = downloadLimits.get(0); // Take the first one - contentValues.put(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_LIMIT, downloadLimit.getLimit()); - contentValues.put(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_COUNT, downloadLimit.getCount()); - } else { - contentValues.putNull(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_LIMIT); - contentValues.putNull(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_COUNT); + boolean isDirectory = MimeTypeUtil.isFolder(remoteFile.getMimeType()); + contentValues.put(ProviderTableMeta.OCSHARES_IS_DIRECTORY, isDirectory); + contentValues.put(ProviderTableMeta.OCSHARES_ACCOUNT_OWNER, user.getAccountName()); + + if (remoteFile.getSharees().length > 0) { + final var sharee = remoteFile.getSharees()[0]; + contentValues.put(ProviderTableMeta.OCSHARES_SHARE_WITH_DISPLAY_NAME, sharee.getDisplayName()); + + ShareType shareType = sharee.getShareType(); + if (shareType != null) { + contentValues.put(ProviderTableMeta.OCSHARES_SHARE_TYPE, shareType.getValue()); + } + + contentValues.put(ProviderTableMeta.OCSHARES_USER_ID, sharee.getUserId()); } - // Set default/null values for non-matching OCShare fields - contentValues.putNull(ProviderTableMeta.OCSHARES_FILE_SOURCE); - contentValues.putNull(ProviderTableMeta.OCSHARES_ITEM_SOURCE); - contentValues.putNull(ProviderTableMeta.OCSHARES_SHARE_TYPE); - contentValues.putNull(ProviderTableMeta.OCSHARES_SHARE_WITH); - contentValues.putNull(ProviderTableMeta.OCSHARES_SHARED_DATE); - contentValues.putNull(ProviderTableMeta.OCSHARES_EXPIRATION_DATE); - contentValues.putNull(ProviderTableMeta.OCSHARES_TOKEN); - contentValues.putNull(ProviderTableMeta.OCSHARES_SHARE_WITH_DISPLAY_NAME); - contentValues.putNull(ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED); - contentValues.put(ProviderTableMeta.OCSHARES_ACCOUNT_OWNER, user.getAccountName()); // Assuming user is available - contentValues.put(ProviderTableMeta.OCSHARES_IS_PASSWORD_PROTECTED, 0); - contentValues.putNull(ProviderTableMeta.OCSHARES_SHARE_LINK); - contentValues.putNull(ProviderTableMeta.OCSHARES_SHARE_LABEL); - contentValues.putNull(ProviderTableMeta.OCSHARES_ATTRIBUTES); + if (!remoteFile.getFileDownloadLimit().isEmpty()) { + FileDownloadLimit downloadLimit = remoteFile.getFileDownloadLimit().get(0); + if (downloadLimit != null) { + contentValues.put(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_LIMIT, downloadLimit.getLimit()); + contentValues.put(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_COUNT, downloadLimit.getCount()); + } else { + contentValues.putNull(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_LIMIT); + contentValues.putNull(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_COUNT); + } + } return contentValues; } @@ -1650,25 +1644,6 @@ private OCShare createShareInstance(Cursor cursor) { return share; } - private void resetShareFlagsInAllFiles() { - ContentValues cv = new ContentValues(); - cv.put(ProviderTableMeta.FILE_SHARED_VIA_LINK, Boolean.FALSE); - cv.put(ProviderTableMeta.FILE_SHARED_WITH_SHAREE, Boolean.FALSE); - String where = ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?"; - String[] whereArgs = new String[]{user.getAccountName()}; - - if (getContentResolver() != null) { - getContentResolver().update(ProviderTableMeta.CONTENT_URI, cv, where, whereArgs); - - } else { - try { - getContentProviderClient().update(ProviderTableMeta.CONTENT_URI, cv, where, whereArgs); - } catch (RemoteException e) { - Log_OC.e(TAG, "Exception in resetShareFlagsInAllFiles" + e.getMessage(), e); - } - } - } - private void resetShareFlagsInFolder(OCFile folder) { ContentValues contentValues = new ContentValues(); contentValues.put(ProviderTableMeta.FILE_SHARED_VIA_LINK, Boolean.FALSE); @@ -1786,10 +1761,16 @@ public void removeShare(OCShare share) { } public void saveSharesFromRemoteFile(List shares) { + if (shares == null || shares.isEmpty()) { + return; + } + final ArrayList operations = prepareInsertSharesFromRemoteFile(shares); + if (operations.isEmpty()) { return; } + applyBatch(operations); } diff --git a/app/src/main/java/com/owncloud/android/providers/FileContentProvider.java b/app/src/main/java/com/owncloud/android/providers/FileContentProvider.java index 016f81f24ae8..19e5fc0b2f4a 100644 --- a/app/src/main/java/com/owncloud/android/providers/FileContentProvider.java +++ b/app/src/main/java/com/owncloud/android/providers/FileContentProvider.java @@ -345,7 +345,13 @@ private Uri insert(SupportSQLiteDatabase db, Uri uri, ContentValues values) { private void updateFilesTableAccordingToShareInsertion(SupportSQLiteDatabase db, ContentValues newShare) { ContentValues fileValues = new ContentValues(); - ShareType newShareType = ShareType.fromValue(newShare.getAsInteger(ProviderTableMeta.OCSHARES_SHARE_TYPE)); + Integer shareTypeValue = newShare.getAsInteger(ProviderTableMeta.OCSHARES_SHARE_TYPE); + if (shareTypeValue == null) { + Log_OC.w(TAG, "Share type is null. Skipping file update."); + return; + } + + ShareType newShareType = ShareType.fromValue(shareTypeValue); switch (newShareType) { case PUBLIC_LINK: From 8445793a765bcd322c1acef4d62d849ccfe318a7 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Wed, 11 Jun 2025 13:46:17 +0200 Subject: [PATCH 03/31] refreshSharesForFolder in share details Signed-off-by: alperozturk --- .../fragment/FileDetailSharingFragment.java | 7 ++- .../fragment/share/RemoteShareRepository.kt | 43 +++++++++++++++++++ .../ui/fragment/share/ShareRepository.kt | 12 ++++++ 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt create mode 100644 app/src/main/java/com/owncloud/android/ui/fragment/share/ShareRepository.kt diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java index 3f99c825c388..9abc0b86fd2c 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java @@ -61,6 +61,8 @@ import com.owncloud.android.ui.adapter.ShareeListAdapterListener; import com.owncloud.android.ui.asynctasks.RetrieveHoverCardAsyncTask; import com.owncloud.android.ui.dialog.SharePasswordDialogFragment; +import com.owncloud.android.ui.fragment.share.RemoteShareRepository; +import com.owncloud.android.ui.fragment.share.ShareRepository; import com.owncloud.android.ui.fragment.util.FileDetailSharingFragmentHelper; import com.owncloud.android.ui.helpers.FileOperationsHelper; import com.owncloud.android.utils.ClipboardUtil; @@ -150,6 +152,10 @@ public void onCreate(@Nullable Bundle savedInstanceState) { if (fileActivity == null) { throw new IllegalArgumentException("FileActivity may not be null"); } + + fileDataStorageManager = fileActivity.getStorageManager(); + ShareRepository shareRepository = new RemoteShareRepository(fileActivity.getClientRepository(), fileActivity, fileDataStorageManager); + shareRepository.refreshSharesForFolder(file.getParentRemotePath()); } @Override @@ -165,7 +171,6 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, binding = FileDetailsSharingFragmentBinding.inflate(inflater, container, false); fileOperationsHelper = fileActivity.getFileOperationsHelper(); - fileDataStorageManager = fileActivity.getStorageManager(); AccountManager accountManager = AccountManager.get(requireContext()); String userId = accountManager.getUserData(user.toPlatformAccount(), diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt b/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt new file mode 100644 index 000000000000..8a99904a9fc2 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt @@ -0,0 +1,43 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.ui.fragment.share + +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import com.nextcloud.repository.ClientRepository +import com.owncloud.android.datamodel.FileDataStorageManager +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.operations.GetSharesForFileOperation +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class RemoteShareRepository( + private val clientRepository: ClientRepository, + lifecycleOwner: LifecycleOwner, + private val fileDataStorageManager: FileDataStorageManager +) : ShareRepository { + private val tag = "RemoteShareRepository" + private val scope = lifecycleOwner.lifecycleScope + + override fun refreshSharesForFolder(remotePath: String) { + scope.launch(Dispatchers.IO) { + val client = clientRepository.getOwncloudClient() ?: return@launch + val operation = GetSharesForFileOperation(remotePath, true, true, fileDataStorageManager) + val result = operation.execute(client) + + if (result.isSuccess) { + Log_OC.d(tag, "Successfully refreshed shares for the specified remote path."); + } else { + Log_OC.w( + tag, + "Failed to refresh shares for the specified remote path. An error occurred during the operation." + ); + } + } + } +} diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/share/ShareRepository.kt b/app/src/main/java/com/owncloud/android/ui/fragment/share/ShareRepository.kt new file mode 100644 index 000000000000..d8b5bcf7c826 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/fragment/share/ShareRepository.kt @@ -0,0 +1,12 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.ui.fragment.share + +interface ShareRepository { + fun refreshSharesForFolder(remotePath: String) +} From 8964648510b606901f24abe396a4f8bbfbdbf458 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Wed, 11 Jun 2025 14:08:34 +0200 Subject: [PATCH 04/31] fix wrong usages Signed-off-by: alperozturk --- .../owncloud/android/datamodel/FileDataStorageManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 2cb9ae8312e4..317854cad9b2 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -1544,17 +1544,17 @@ private ContentValues createContentValueForRemoteFile(RemoteFile remoteFile) { boolean isDirectory = MimeTypeUtil.isFolder(remoteFile.getMimeType()); contentValues.put(ProviderTableMeta.OCSHARES_IS_DIRECTORY, isDirectory); contentValues.put(ProviderTableMeta.OCSHARES_ACCOUNT_OWNER, user.getAccountName()); + contentValues.put(ProviderTableMeta.OCSHARES_USER_ID, remoteFile.getOwnerId()); if (remoteFile.getSharees().length > 0) { final var sharee = remoteFile.getSharees()[0]; contentValues.put(ProviderTableMeta.OCSHARES_SHARE_WITH_DISPLAY_NAME, sharee.getDisplayName()); + contentValues.put(ProviderTableMeta.OCSHARES_SHARE_WITH, sharee.getUserId()); ShareType shareType = sharee.getShareType(); if (shareType != null) { contentValues.put(ProviderTableMeta.OCSHARES_SHARE_TYPE, shareType.getValue()); } - - contentValues.put(ProviderTableMeta.OCSHARES_USER_ID, sharee.getUserId()); } if (!remoteFile.getFileDownloadLimit().isEmpty()) { From 0b510c4c91c9107e91554a7695e21722c1f5c7e2 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Wed, 11 Jun 2025 14:35:15 +0200 Subject: [PATCH 05/31] fix wrong usages Signed-off-by: alperozturk --- .../com/owncloud/android/datamodel/FileDataStorageManager.java | 1 + 1 file changed, 1 insertion(+) 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 317854cad9b2..82320eb61fca 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -1545,6 +1545,7 @@ private ContentValues createContentValueForRemoteFile(RemoteFile remoteFile) { contentValues.put(ProviderTableMeta.OCSHARES_IS_DIRECTORY, isDirectory); contentValues.put(ProviderTableMeta.OCSHARES_ACCOUNT_OWNER, user.getAccountName()); contentValues.put(ProviderTableMeta.OCSHARES_USER_ID, remoteFile.getOwnerId()); + contentValues.put(ProviderTableMeta.OCSHARES_NOTE, remoteFile.getNote()); if (remoteFile.getSharees().length > 0) { final var sharee = remoteFile.getSharees()[0]; From ac0b16019e7d25732dda3518f40ac604be9aed08 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Thu, 12 Jun 2025 10:06:49 +0200 Subject: [PATCH 06/31] use lib Signed-off-by: alperozturk --- .../android/datamodel/FileDataStorageManager.java | 5 +++++ gradle/verification-metadata.xml | 8 ++++++++ 2 files changed, 13 insertions(+) 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 82320eb61fca..fd33ea425ac7 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -1540,6 +1540,11 @@ public List getSharesByPathAndType(String path, ShareType type, String private ContentValues createContentValueForRemoteFile(RemoteFile remoteFile) { ContentValues contentValues = new ContentValues(); + OCFile ocFile = getFileByDecryptedRemotePath(remoteFile.getRemotePath()); + if (ocFile != null) { + contentValues.put(ProviderTableMeta.OCSHARES_FILE_SOURCE, ocFile.getFileId()); + } + contentValues.put(ProviderTableMeta.OCSHARES_PATH, remoteFile.getRemotePath()); boolean isDirectory = MimeTypeUtil.isFolder(remoteFile.getMimeType()); contentValues.put(ProviderTableMeta.OCSHARES_IS_DIRECTORY, isDirectory); diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 449a55ed7449..a283aa47aa43 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -14213,6 +14213,14 @@ + + + + + + + + From a14466fdc120281e5c1e50442d16e5ba8a22a934 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Thu, 12 Jun 2025 10:11:55 +0200 Subject: [PATCH 07/31] remove unused func Signed-off-by: alperozturk --- .../android/datamodel/FileDataStorageManager.java | 15 --------------- 1 file changed, 15 deletions(-) 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 fd33ea425ac7..c3f785a5249a 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -39,7 +39,6 @@ import com.nextcloud.client.database.dao.OfflineOperationDao; import com.nextcloud.client.database.entity.FileEntity; import com.nextcloud.client.database.entity.OfflineOperationEntity; -import com.nextcloud.client.database.entity.ShareEntity; import com.nextcloud.client.jobs.offlineOperations.repository.OfflineOperationsRepository; import com.nextcloud.client.jobs.offlineOperations.repository.OfflineOperationsRepositoryType; import com.nextcloud.model.OCFileFilterType; @@ -84,7 +83,6 @@ import java.util.List; import java.util.Locale; import java.util.Set; -import java.util.function.Consumer; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -1820,19 +1818,6 @@ public void saveSharesDB(List shares) { applyBatch(operations); } - // TODO check: - private void resetShareFlags(List sharePaths) { - ArrayList operations = new ArrayList<>(); - String filePath = ""; - for (String sharePath : sharePaths) { - if (!filePath.equals(sharePath)) { - filePath = sharePath; - resetShareFlagInAFile(filePath); - prepareRemoveSharesInFile(filePath, operations); - } - } - } - private void applyBatch(ArrayList operations) { try { if (getContentResolver() != null) { From 891b96afbf52e992c5017656fc03d8cefb9d36f5 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Thu, 12 Jun 2025 10:19:00 +0200 Subject: [PATCH 08/31] fix kt spotless Signed-off-by: alperozturk --- .../android/ui/fragment/share/RemoteShareRepository.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt b/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt index 8a99904a9fc2..302e4d0ac5e6 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt @@ -31,12 +31,12 @@ class RemoteShareRepository( val result = operation.execute(client) if (result.isSuccess) { - Log_OC.d(tag, "Successfully refreshed shares for the specified remote path."); + Log_OC.d(tag, "Successfully refreshed shares for the specified remote path.") } else { Log_OC.w( tag, "Failed to refresh shares for the specified remote path. An error occurred during the operation." - ); + ) } } } From 44b2714ba230e821593474221f3a1333a7ada9c2 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Thu, 12 Jun 2025 15:38:33 +0200 Subject: [PATCH 09/31] implement feedbacks Signed-off-by: alperozturk --- .../android/ui/fragment/FileDetailSharingFragment.java | 2 +- .../android/ui/fragment/share/RemoteShareRepository.kt | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java index 9abc0b86fd2c..1b3f08f8bb2a 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java @@ -155,7 +155,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) { fileDataStorageManager = fileActivity.getStorageManager(); ShareRepository shareRepository = new RemoteShareRepository(fileActivity.getClientRepository(), fileActivity, fileDataStorageManager); - shareRepository.refreshSharesForFolder(file.getParentRemotePath()); + shareRepository.refreshSharesForFolder(file.getRemotePath()); } @Override diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt b/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt index 302e4d0ac5e6..876a42332ea4 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt @@ -27,9 +27,13 @@ class RemoteShareRepository( override fun refreshSharesForFolder(remotePath: String) { scope.launch(Dispatchers.IO) { val client = clientRepository.getOwncloudClient() ?: return@launch - val operation = GetSharesForFileOperation(remotePath, true, true, fileDataStorageManager) + val operation = GetSharesForFileOperation(remotePath, true, false, fileDataStorageManager) + + @Suppress("DEPRECATION") val result = operation.execute(client) + Log_OC.i(tag, "Remote path for the refresh shares: $remotePath") + if (result.isSuccess) { Log_OC.d(tag, "Successfully refreshed shares for the specified remote path.") } else { From 8b96d24f4917a0188525c9af7b8dd18e087e15ae Mon Sep 17 00:00:00 2001 From: alperozturk Date: Thu, 12 Jun 2025 15:41:07 +0200 Subject: [PATCH 10/31] indicate parameters Signed-off-by: alperozturk --- .../android/ui/fragment/share/RemoteShareRepository.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt b/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt index 876a42332ea4..1f1687fc6927 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt @@ -27,7 +27,13 @@ class RemoteShareRepository( override fun refreshSharesForFolder(remotePath: String) { scope.launch(Dispatchers.IO) { val client = clientRepository.getOwncloudClient() ?: return@launch - val operation = GetSharesForFileOperation(remotePath, true, false, fileDataStorageManager) + val operation = + GetSharesForFileOperation( + path = remotePath, + reshares = true, + subfiles = false, + storageManager = fileDataStorageManager + ) @Suppress("DEPRECATION") val result = operation.execute(client) From 4a70e9ed80b0c49f16b3e6f518dfe925406641b6 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 13 Jun 2025 09:15:57 +0200 Subject: [PATCH 11/31] fix FileDownloadLimit crash Signed-off-by: alperozturk --- .../datamodel/FileDataStorageManager.java | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) 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 c3f785a5249a..e37de3d3d8e1 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -111,6 +111,7 @@ public class FileDataStorageManager { public final FileDao fileDao = NextcloudDatabase.getInstance(MainApp.getAppContext()).fileDao(); private final Gson gson = new Gson(); public final OfflineOperationsRepositoryType offlineOperationsRepository; + private final static int DEFAULT_CURSOR_INT_VALUE = -1; public FileDataStorageManager(User user, ContentResolver contentResolver) { this.contentProviderClient = null; @@ -1638,16 +1639,47 @@ private OCShare createShareInstance(Cursor cursor) { share.setShareLink(getString(cursor, ProviderTableMeta.OCSHARES_SHARE_LINK)); share.setLabel(getString(cursor, ProviderTableMeta.OCSHARES_SHARE_LABEL)); - FileDownloadLimit downloadLimit = new FileDownloadLimit(token, - getInt(cursor, ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_LIMIT), - getInt(cursor, ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_COUNT)); - share.setFileDownloadLimit(downloadLimit); + FileDownloadLimit fileDownloadLimit = getDownloadLimitFromCursor(cursor, token); + if (fileDownloadLimit != null) { + share.setFileDownloadLimit(fileDownloadLimit); + } share.setAttributes(getString(cursor, ProviderTableMeta.OCSHARES_ATTRIBUTES)); return share; } + @Nullable + private FileDownloadLimit getDownloadLimitFromCursor(Cursor cursor, String token) { + int limit = getIntOrDefault(cursor, ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_LIMIT); + int count = getIntOrDefault(cursor, ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_COUNT); + if (limit != DEFAULT_CURSOR_INT_VALUE && count != DEFAULT_CURSOR_INT_VALUE) { + return new FileDownloadLimit(token, limit, count); + } + + return null; + } + + /** + * Retrieves an integer value from the specified column in the cursor. + *

+ * If the column does not exist (i.e., {@code cursor.getColumnIndex(columnName)} returns -1), + * this method returns {@code -1} as a default value. + *

+ * + * @param cursor The Cursor from which to retrieve the value. + * @param columnName The name of the column to retrieve the integer from. + * @return The integer value from the column, or {@code -1} if the column is not found. + */ + private int getIntOrDefault(Cursor cursor, String columnName) { + int index = cursor.getColumnIndex(columnName); + if (index == DEFAULT_CURSOR_INT_VALUE) { + return DEFAULT_CURSOR_INT_VALUE; + } + + return cursor.getInt(index); + } + private void resetShareFlagsInFolder(OCFile folder) { ContentValues contentValues = new ContentValues(); contentValues.put(ProviderTableMeta.FILE_SHARED_VIA_LINK, Boolean.FALSE); From a3116ede4ac4d3e08eb53210c93c70aa31aeab58 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 13 Jun 2025 09:29:53 +0200 Subject: [PATCH 12/31] setDownloadLimitToContentValues Signed-off-by: alperozturk --- .../datamodel/FileDataStorageManager.java | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) 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 e37de3d3d8e1..1d6d03f85f34 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -1564,13 +1564,7 @@ private ContentValues createContentValueForRemoteFile(RemoteFile remoteFile) { if (!remoteFile.getFileDownloadLimit().isEmpty()) { FileDownloadLimit downloadLimit = remoteFile.getFileDownloadLimit().get(0); - if (downloadLimit != null) { - contentValues.put(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_LIMIT, downloadLimit.getLimit()); - contentValues.put(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_COUNT, downloadLimit.getCount()); - } else { - contentValues.putNull(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_LIMIT); - contentValues.putNull(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_COUNT); - } + setDownloadLimitToContentValues(contentValues, downloadLimit); } return contentValues; @@ -1604,13 +1598,7 @@ private ContentValues createContentValueForShare(OCShare share) { contentValues.put(ProviderTableMeta.OCSHARES_SHARE_LABEL, share.getLabel()); FileDownloadLimit downloadLimit = share.getFileDownloadLimit(); - if (downloadLimit != null) { - contentValues.put(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_LIMIT, downloadLimit.getLimit()); - contentValues.put(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_COUNT, downloadLimit.getCount()); - } else { - contentValues.putNull(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_LIMIT); - contentValues.putNull(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_COUNT); - } + setDownloadLimitToContentValues(contentValues, downloadLimit); contentValues.put(ProviderTableMeta.OCSHARES_ATTRIBUTES, share.getAttributes()); @@ -1649,6 +1637,17 @@ private OCShare createShareInstance(Cursor cursor) { return share; } + private void setDownloadLimitToContentValues(ContentValues contentValues, FileDownloadLimit downloadLimit) { + if (downloadLimit != null) { + contentValues.put(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_LIMIT, downloadLimit.getLimit()); + contentValues.put(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_COUNT, downloadLimit.getCount()); + return; + } + + contentValues.putNull(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_LIMIT); + contentValues.putNull(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_COUNT); + } + @Nullable private FileDownloadLimit getDownloadLimitFromCursor(Cursor cursor, String token) { int limit = getIntOrDefault(cursor, ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_LIMIT); From 0656dc7909cbcfed0cc94c72c57fa60a96be7947 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 13 Jun 2025 09:51:22 +0200 Subject: [PATCH 13/31] support avatar generation for external shares Signed-off-by: alperozturk --- .../owncloud/android/utils/DisplayUtils.java | 49 +++++++++++-------- 1 file changed, 29 insertions(+), 20 deletions(-) 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 25cac5ed2ac8..60b34db026e6 100644 --- a/app/src/main/java/com/owncloud/android/utils/DisplayUtils.java +++ b/app/src/main/java/com/owncloud/android/utils/DisplayUtils.java @@ -467,30 +467,39 @@ public static void setAvatar(@NonNull User user, Resources resources, Object callContext, Context context) { - if (callContext instanceof View) { - ((View) callContext).setContentDescription(String.valueOf(user.toPlatformAccount().hashCode())); + if (callContext instanceof View v) { + v.setContentDescription(String.valueOf(user.toPlatformAccount().hashCode())); } - ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(context); - final String accountName = user.getAccountName(); String serverName = accountName.substring(accountName.lastIndexOf('@') + 1); - String eTag = arbitraryDataProvider.getValue(userId + "@" + serverName, ThumbnailsCacheManager.AVATAR); - String avatarKey = "a_" + userId + "_" + serverName + "_" + eTag; - - // first show old one - Drawable avatar = BitmapUtils.bitmapToCircularBitmapDrawable(resources, - ThumbnailsCacheManager.getBitmapFromDiskCache(avatarKey)); - - // if no one exists, show colored icon with initial char - if (avatar == null) { - try { - avatar = TextDrawable.createAvatarByUserId(displayName, avatarRadius); - } catch (Exception e) { - Log_OC.e(TAG, "Error calculating RGB value for active account icon.", e); - avatar = ResourcesCompat.getDrawable(resources, - R.drawable.account_circle_white, - null); + Drawable avatar; + + if (userId.isEmpty()) { + avatar = ContextCompat.getDrawable(context, R.drawable.ic_link); + if (avatar != null) { + int tintColor = ContextCompat.getColor(context, R.color.icon_on_nc_grey); + avatar.setTint(tintColor); + } + } else { + ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(context); + String eTag = arbitraryDataProvider.getValue(userId + "@" + serverName, ThumbnailsCacheManager.AVATAR); + String avatarKey = "a_" + userId + "_" + serverName + "_" + eTag; + + // first show old one + avatar = BitmapUtils.bitmapToCircularBitmapDrawable(resources, + ThumbnailsCacheManager.getBitmapFromDiskCache(avatarKey)); + + // if no one exists, show colored icon with initial char + if (avatar == null) { + try { + avatar = TextDrawable.createAvatarByUserId(displayName, avatarRadius); + } catch (Exception e) { + Log_OC.e(TAG, "Error calculating RGB value for active account icon.", e); + avatar = ResourcesCompat.getDrawable(resources, + R.drawable.account_circle_white, + null); + } } } From edf5dcbc823ecafe9205cfff3b6dd687e8c1d8ab Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 13 Jun 2025 10:40:46 +0200 Subject: [PATCH 14/31] fix operation creation from remotefile object Signed-off-by: alperozturk --- .../java/com/nextcloud/model/ShareeEntry.kt | 73 +++++++++++++++++++ .../datamodel/FileDataStorageManager.java | 59 ++++++--------- 2 files changed, 94 insertions(+), 38 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/model/ShareeEntry.kt diff --git a/app/src/main/java/com/nextcloud/model/ShareeEntry.kt b/app/src/main/java/com/nextcloud/model/ShareeEntry.kt new file mode 100644 index 000000000000..6ff1274178e6 --- /dev/null +++ b/app/src/main/java/com/nextcloud/model/ShareeEntry.kt @@ -0,0 +1,73 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.model + +import android.content.ContentValues +import com.owncloud.android.db.ProviderMeta.ProviderTableMeta +import com.owncloud.android.lib.resources.files.model.RemoteFile +import com.owncloud.android.lib.resources.shares.ShareType + +data class ShareeEntry( + val filePath: String?, + val accountOwner: String, + val fileOwnerId: String?, + val shareWithDisplayName: String?, + val shareWithUserId: String?, + val shareType: Int +) { + companion object { + /** + * Extracts a list of share-related ContentValues from a given RemoteFile. + * + * Each RemoteFile can be shared with multiple users (sharees), and this function converts each + * sharee into a ContentValues object, representing a row for insertion into a database. + * + * @param remoteFile The RemoteFile object containing sharee information. + * @param accountName The name of the user account that owns this RemoteFile. + * @return A list of ContentValues representing each share entry, or null if no sharees are found. + */ + fun getContentValues(remoteFile: RemoteFile, accountName: String): List? { + if (remoteFile.sharees.isNullOrEmpty()) { + return null + } + + val result = arrayListOf() + + for (share in remoteFile.sharees) { + val shareType: ShareType? = share.shareType + if (shareType == null) { + continue + } + + val contentValue = ShareeEntry( + remoteFile.remotePath, + accountName, + remoteFile.ownerId, + share.displayName, + share.userId, + shareType.value + ).toContentValues() + + result.add(contentValue) + } + + return result + } + } + + private fun toContentValues(): ContentValues { + return ContentValues().apply { + put(ProviderTableMeta.OCSHARES_PATH, filePath) + put(ProviderTableMeta.OCSHARES_ACCOUNT_OWNER, accountOwner) + put(ProviderTableMeta.OCSHARES_USER_ID, fileOwnerId) + put(ProviderTableMeta.OCSHARES_SHARE_WITH_DISPLAY_NAME, shareWithDisplayName) + put(ProviderTableMeta.OCSHARES_SHARE_WITH, shareWithUserId) + put(ProviderTableMeta.OCSHARES_SHARE_TYPE, shareType) + } + } +} 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 1d6d03f85f34..25ec6087c3f4 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -44,6 +44,7 @@ import com.nextcloud.model.OCFileFilterType; import com.nextcloud.model.OfflineOperationRawType; import com.nextcloud.model.OfflineOperationType; +import com.nextcloud.model.ShareeEntry; import com.nextcloud.utils.date.DateFormatPattern; import com.nextcloud.utils.extensions.DateExtensionsKt; import com.owncloud.android.MainApp; @@ -1536,40 +1537,6 @@ public List getSharesByPathAndType(String path, ShareType type, String return shares; } - private ContentValues createContentValueForRemoteFile(RemoteFile remoteFile) { - ContentValues contentValues = new ContentValues(); - - OCFile ocFile = getFileByDecryptedRemotePath(remoteFile.getRemotePath()); - if (ocFile != null) { - contentValues.put(ProviderTableMeta.OCSHARES_FILE_SOURCE, ocFile.getFileId()); - } - - contentValues.put(ProviderTableMeta.OCSHARES_PATH, remoteFile.getRemotePath()); - boolean isDirectory = MimeTypeUtil.isFolder(remoteFile.getMimeType()); - contentValues.put(ProviderTableMeta.OCSHARES_IS_DIRECTORY, isDirectory); - contentValues.put(ProviderTableMeta.OCSHARES_ACCOUNT_OWNER, user.getAccountName()); - contentValues.put(ProviderTableMeta.OCSHARES_USER_ID, remoteFile.getOwnerId()); - contentValues.put(ProviderTableMeta.OCSHARES_NOTE, remoteFile.getNote()); - - if (remoteFile.getSharees().length > 0) { - final var sharee = remoteFile.getSharees()[0]; - contentValues.put(ProviderTableMeta.OCSHARES_SHARE_WITH_DISPLAY_NAME, sharee.getDisplayName()); - contentValues.put(ProviderTableMeta.OCSHARES_SHARE_WITH, sharee.getUserId()); - - ShareType shareType = sharee.getShareType(); - if (shareType != null) { - contentValues.put(ProviderTableMeta.OCSHARES_SHARE_TYPE, shareType.getValue()); - } - } - - if (!remoteFile.getFileDownloadLimit().isEmpty()) { - FileDownloadLimit downloadLimit = remoteFile.getFileDownloadLimit().get(0); - setDownloadLimitToContentValues(contentValues, downloadLimit); - } - - return contentValues; - } - private ContentValues createContentValueForShare(OCShare share) { ContentValues contentValues = new ContentValues(); contentValues.put(ProviderTableMeta.OCSHARES_FILE_SOURCE, share.getFileSource()); @@ -1809,12 +1776,28 @@ public void saveSharesFromRemoteFile(List shares) { applyBatch(operations); } + /** + * Prepares a list of ContentProviderOperation insert operations based on share information + * found in the given iterable of RemoteFile objects. + * + * Each RemoteFile may have multiple share entries (sharees), and for each one, + * a corresponding ContentProviderOperation is created for insertion into the shares table. + * + * @param remoteFiles An iterable list of RemoteFile objects containing sharee data. + * @return A list of ContentProviderOperation objects for batch insertion into the content provider. + */ private ArrayList prepareInsertSharesFromRemoteFile(Iterable remoteFiles) { - final ArrayList operations = new ArrayList<>(); - - ContentValues contentValues; + final ArrayList contentValueList = new ArrayList<>(); for (RemoteFile remoteFile : remoteFiles) { - contentValues = createContentValueForRemoteFile(remoteFile); + final var contentValues = ShareeEntry.Companion.getContentValues(remoteFile, user.getAccountName()); + if (contentValues == null) { + continue; + } + contentValueList.addAll(contentValues); + } + + final ArrayList operations = new ArrayList<>(); + for (ContentValues contentValues : contentValueList) { operations.add(ContentProviderOperation .newInsert(ProviderTableMeta.CONTENT_URI_SHARE) .withValues(contentValues) From bcce01d22f6c8455944c2a193b1058e0863cd7bf Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 13 Jun 2025 10:40:56 +0200 Subject: [PATCH 15/31] fix operation creation from remotefile object Signed-off-by: alperozturk --- .../com/owncloud/android/datamodel/FileDataStorageManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 25ec6087c3f4..eebf56ac42c9 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -1779,7 +1779,7 @@ public void saveSharesFromRemoteFile(List shares) { /** * Prepares a list of ContentProviderOperation insert operations based on share information * found in the given iterable of RemoteFile objects. - * + *

* Each RemoteFile may have multiple share entries (sharees), and for each one, * a corresponding ContentProviderOperation is created for insertion into the shares table. * From 609b8f3df413f32bd1f5da6cac94c575e1e678ce Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 13 Jun 2025 11:05:04 +0200 Subject: [PATCH 16/31] fix ss test, pass correct argument Signed-off-by: alperozturk --- .../android/ui/adapter/OCShareToOCFileConverter.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCShareToOCFileConverter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/OCShareToOCFileConverter.kt index 186e628e7f8a..affc513870b7 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCShareToOCFileConverter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCShareToOCFileConverter.kt @@ -63,7 +63,13 @@ object OCShareToOCFileConverter { file.isSharedWithSharee = true file.sharees = shares .filter { it.shareType != ShareType.PUBLIC_LINK && it.shareType != ShareType.EMAIL } - .map { ShareeUser(it.shareWith, it.sharedWithDisplayName, it.shareType) } + .map { + ShareeUser( + userId = it.userId, + displayName = it.sharedWithDisplayName, + shareType = it.shareType + ) + } } return file } From f485300e534a22ca88f7520d0a1ecea20e55bad3 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 13 Jun 2025 12:10:49 +0200 Subject: [PATCH 17/31] check token NPE Signed-off-by: alperozturk --- app/src/main/java/com/nextcloud/model/ShareeEntry.kt | 2 +- .../owncloud/android/datamodel/FileDataStorageManager.java | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/nextcloud/model/ShareeEntry.kt b/app/src/main/java/com/nextcloud/model/ShareeEntry.kt index 6ff1274178e6..327cd9b23dea 100644 --- a/app/src/main/java/com/nextcloud/model/ShareeEntry.kt +++ b/app/src/main/java/com/nextcloud/model/ShareeEntry.kt @@ -39,7 +39,7 @@ data class ShareeEntry( val result = arrayListOf() for (share in remoteFile.sharees) { - val shareType: ShareType? = share.shareType + val shareType: ShareType? = share?.shareType if (shareType == null) { continue } 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 eebf56ac42c9..495fa478cfbf 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -1617,6 +1617,10 @@ private void setDownloadLimitToContentValues(ContentValues contentValues, FileDo @Nullable private FileDownloadLimit getDownloadLimitFromCursor(Cursor cursor, String token) { + if (token == null || cursor == null) { + return null; + } + int limit = getIntOrDefault(cursor, ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_LIMIT); int count = getIntOrDefault(cursor, ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_COUNT); if (limit != DEFAULT_CURSOR_INT_VALUE && count != DEFAULT_CURSOR_INT_VALUE) { From b7010ea35ea51cfb9b0581c66950261a7ba5fe6b Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 13 Jun 2025 13:32:26 +0200 Subject: [PATCH 18/31] listen completions of the remote operation Signed-off-by: alperozturk --- .../datamodel/FileDataStorageManager.java | 26 +++++++++++++++---- .../fragment/FileDetailSharingFragment.java | 20 +++++++------- .../fragment/share/RemoteShareRepository.kt | 21 +++++++++------ .../ui/fragment/share/ShareRepository.kt | 2 +- app/src/main/res/values/strings.xml | 1 + 5 files changed, 46 insertions(+), 24 deletions(-) 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 495fa478cfbf..1758d033eede 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -1771,13 +1771,29 @@ public void saveSharesFromRemoteFile(List shares) { return; } - final ArrayList operations = prepareInsertSharesFromRemoteFile(shares); + // Prepare reset operations + Set uniquePaths = new HashSet<>(); + for (RemoteFile share : shares) { + uniquePaths.add(share.getRemotePath()); + } - if (operations.isEmpty()) { - return; + ArrayList resetOperations = new ArrayList<>(); + for (String path : uniquePaths) { + resetShareFlagInAFile(path); + var removeOps = prepareRemoveSharesInFile(path, new ArrayList<>()); + if (!removeOps.isEmpty()) { + resetOperations.addAll(removeOps); + } + } + if (!resetOperations.isEmpty()) { + applyBatch(resetOperations); } - applyBatch(operations); + // Prepare insert operations + ArrayList insertOperations = prepareInsertSharesFromRemoteFile(shares); + if (!insertOperations.isEmpty()) { + applyBatch(insertOperations); + } } /** @@ -1800,7 +1816,7 @@ private ArrayList prepareInsertSharesFromRemoteFile(It contentValueList.addAll(contentValues); } - final ArrayList operations = new ArrayList<>(); + ArrayList operations = new ArrayList<>(); for (ContentValues contentValues : contentValueList) { operations.add(ContentProviderOperation .newInsert(ProviderTableMeta.CONTENT_URI_SHARE) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java index 1b3f08f8bb2a..93021a699747 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java @@ -84,6 +84,7 @@ import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.LinearLayoutManager; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import kotlin.Unit; public class FileDetailSharingFragment extends Fragment implements ShareeListAdapterListener, DisplayUtils.AvatarGenerationListener, @@ -155,15 +156,14 @@ public void onCreate(@Nullable Bundle savedInstanceState) { fileDataStorageManager = fileActivity.getStorageManager(); ShareRepository shareRepository = new RemoteShareRepository(fileActivity.getClientRepository(), fileActivity, fileDataStorageManager); - shareRepository.refreshSharesForFolder(file.getRemotePath()); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - refreshCapabilitiesFromDB(); - refreshSharesFromDB(); + shareRepository.refreshSharesForFile(file.getRemotePath(), () -> { + refreshCapabilitiesFromDB(); + refreshSharesFromDB(); + return Unit.INSTANCE; + }, () -> { + DisplayUtils.showSnackMessage(getView(), R.string.error_fetching_sharees); + return Unit.INSTANCE; + }); } @Override @@ -201,7 +201,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, SharesType.EXTERNAL); externalShareeListAdapter.setHasStableIds(true); - + binding.sharesListExternal.setAdapter(externalShareeListAdapter); binding.sharesListExternal.setLayoutManager(new LinearLayoutManager(requireContext())); diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt b/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt index 1f1687fc6927..ac4bf089c7fd 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt @@ -15,6 +15,7 @@ import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.operations.GetSharesForFileOperation import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext class RemoteShareRepository( private val clientRepository: ClientRepository, @@ -24,7 +25,7 @@ class RemoteShareRepository( private val tag = "RemoteShareRepository" private val scope = lifecycleOwner.lifecycleScope - override fun refreshSharesForFolder(remotePath: String) { + override fun refreshSharesForFile(remotePath: String, onCompleted: () -> Unit, onError: () -> Unit) { scope.launch(Dispatchers.IO) { val client = clientRepository.getOwncloudClient() ?: return@launch val operation = @@ -40,13 +41,17 @@ class RemoteShareRepository( Log_OC.i(tag, "Remote path for the refresh shares: $remotePath") - if (result.isSuccess) { - Log_OC.d(tag, "Successfully refreshed shares for the specified remote path.") - } else { - Log_OC.w( - tag, - "Failed to refresh shares for the specified remote path. An error occurred during the operation." - ) + withContext(Dispatchers.Main) { + if (result.isSuccess) { + Log_OC.d(tag, "Successfully refreshed shares for the specified remote path.") + onCompleted() + } else { + Log_OC.w( + tag, + "Failed to refresh shares for the specified remote path. An error occurred during the operation." + ) + onError() + } } } } diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/share/ShareRepository.kt b/app/src/main/java/com/owncloud/android/ui/fragment/share/ShareRepository.kt index d8b5bcf7c826..a633ea42dbab 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/share/ShareRepository.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/share/ShareRepository.kt @@ -8,5 +8,5 @@ package com.owncloud.android.ui.fragment.share interface ShareRepository { - fun refreshSharesForFolder(remotePath: String) + fun refreshSharesForFile(remotePath: String, onCompleted: () -> Unit, onError: () -> Unit) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index efe459a17eec..3cece078bb3c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -885,6 +885,7 @@ New folder Virus detected. Upload cannot be completed! Tags + Unable to fetch sharees. Adding sharee failed Adding share failed. This file or folder has already been shared with this person or group. Unsharing failed From c1c17ab1d44d76a75d79e11f17209a80b9fa39e8 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 13 Jun 2025 13:45:30 +0200 Subject: [PATCH 19/31] add shimmering Signed-off-by: alperozturk --- .../fragment/FileDetailSharingFragment.java | 4 + .../layout/file_details_sharing_fragment.xml | 323 ++++++++++-------- 2 files changed, 184 insertions(+), 143 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java index 93021a699747..d325f4093d2f 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java @@ -159,8 +159,12 @@ public void onCreate(@Nullable Bundle savedInstanceState) { shareRepository.refreshSharesForFile(file.getRemotePath(), () -> { refreshCapabilitiesFromDB(); refreshSharesFromDB(); + binding.shimmerLayout.setVisibility(View.GONE); + binding.shareContainer.setVisibility(View.VISIBLE); return Unit.INSTANCE; }, () -> { + binding.shimmerLayout.setVisibility(View.GONE); + binding.shareContainer.setVisibility(View.VISIBLE); DisplayUtils.showSnackMessage(getView(), R.string.error_fetching_sharees); return Unit.INSTANCE; }); diff --git a/app/src/main/res/layout/file_details_sharing_fragment.xml b/app/src/main/res/layout/file_details_sharing_fragment.xml index 5398e36ee4f7..82caa4f4ab31 100644 --- a/app/src/main/res/layout/file_details_sharing_fragment.xml +++ b/app/src/main/res/layout/file_details_sharing_fragment.xml @@ -12,182 +12,219 @@ android:layout_height="match_parent" android:paddingTop="@dimen/standard_eight_padding"> - + android:layout_height="match_parent" + android:layout_below="@id/appbar"> - - + android:visibility="gone" + android:orientation="vertical"> - + - - + android:paddingRight="@dimen/standard_padding"> + android:text="@string/shared_with_you_by" + android:textSize="@dimen/two_line_primary_text_size" /> + + + + + + + + + + + + + + + + + - - - - - - - - + android:text="@string/internal_shares" + android:textAppearance="?android:attr/textAppearanceMedium" /> - + - + - + - + - + - - - + + + - - - + - + + - + android:layout_height="match_parent"> + + + + + + + + + + + + + + + + + + - From 03b38f3d8cdf327ebff768f6ccbc342ca5b6d9c4 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 13 Jun 2025 13:57:44 +0200 Subject: [PATCH 20/31] add share_list_item_shimmer.xml Signed-off-by: alperozturk --- .../layout/file_details_sharing_fragment.xml | 39 ++++++++-------- .../res/layout/share_list_item_shimmer.xml | 45 +++++++++++++++++++ 2 files changed, 65 insertions(+), 19 deletions(-) create mode 100644 app/src/main/res/layout/share_list_item_shimmer.xml diff --git a/app/src/main/res/layout/file_details_sharing_fragment.xml b/app/src/main/res/layout/file_details_sharing_fragment.xml index 82caa4f4ab31..d1c2c541e526 100644 --- a/app/src/main/res/layout/file_details_sharing_fragment.xml +++ b/app/src/main/res/layout/file_details_sharing_fragment.xml @@ -196,34 +196,35 @@ android:text="@string/show_all" /> - - - - - + android:layout_height="wrap_content" + android:orientation="vertical"> - + - + - + - + - + - + diff --git a/app/src/main/res/layout/share_list_item_shimmer.xml b/app/src/main/res/layout/share_list_item_shimmer.xml new file mode 100644 index 000000000000..4d4e8c1dd290 --- /dev/null +++ b/app/src/main/res/layout/share_list_item_shimmer.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + From 4ff2cbb9e4900e48caa7d7620b2b9cf80af8b86f Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 13 Jun 2025 14:04:03 +0200 Subject: [PATCH 21/31] add blink animation Signed-off-by: alperozturk --- .../fragment/FileDetailSharingFragment.java | 21 +++++++++++++++---- app/src/main/res/anim/blink.xml | 13 ++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 app/src/main/res/anim/blink.xml diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java index d325f4093d2f..63daa4dec331 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java @@ -30,6 +30,8 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; import com.nextcloud.client.account.User; import com.nextcloud.client.account.UserAccountManager; @@ -159,21 +161,32 @@ public void onCreate(@Nullable Bundle savedInstanceState) { shareRepository.refreshSharesForFile(file.getRemotePath(), () -> { refreshCapabilitiesFromDB(); refreshSharesFromDB(); - binding.shimmerLayout.setVisibility(View.GONE); - binding.shareContainer.setVisibility(View.VISIBLE); + showShareContainer(); return Unit.INSTANCE; }, () -> { - binding.shimmerLayout.setVisibility(View.GONE); - binding.shareContainer.setVisibility(View.VISIBLE); + showShareContainer(); DisplayUtils.showSnackMessage(getView(), R.string.error_fetching_sharees); return Unit.INSTANCE; }); } + private void showShareContainer() { + if (binding == null) { + return; + } + + binding.shimmerLayout.clearAnimation(); + binding.shimmerLayout.setVisibility(View.GONE); + binding.shareContainer.setVisibility(View.VISIBLE); + } + @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { binding = FileDetailsSharingFragmentBinding.inflate(inflater, container, false); + final Animation blinkAnimation = AnimationUtils.loadAnimation(requireContext(), R.anim.blink); + binding.shimmerLayout.startAnimation(blinkAnimation); + fileOperationsHelper = fileActivity.getFileOperationsHelper(); AccountManager accountManager = AccountManager.get(requireContext()); diff --git a/app/src/main/res/anim/blink.xml b/app/src/main/res/anim/blink.xml new file mode 100644 index 000000000000..9a7ff11850a3 --- /dev/null +++ b/app/src/main/res/anim/blink.xml @@ -0,0 +1,13 @@ + + + From 2334dcb213be9c995f330a590f6dec5cf3ca3c13 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 13 Jun 2025 14:05:28 +0200 Subject: [PATCH 22/31] kt spotless fix Signed-off-by: alperozturk --- .../android/ui/fragment/share/RemoteShareRepository.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt b/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt index ac4bf089c7fd..051ac2895651 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt @@ -48,7 +48,8 @@ class RemoteShareRepository( } else { Log_OC.w( tag, - "Failed to refresh shares for the specified remote path. An error occurred during the operation." + "Failed to refresh shares for the specified remote path. " + + "An error occurred during the operation." ) onError() } From 511f9f0b17cb6fba1825c7b383713d630e77fadc Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 13 Jun 2025 15:49:37 +0200 Subject: [PATCH 23/31] extract layouts Signed-off-by: alperozturk --- .../fragment/FileDetailSharingFragment.java | 15 ++++++--- .../fragment/share/RemoteShareRepository.kt | 2 +- .../ui/fragment/share/ShareRepository.kt | 2 +- app/src/main/res/layout/circle_shimmer.xml | 17 ++++++++++ .../layout/file_details_sharing_fragment.xml | 33 +++---------------- .../layout/file_details_sharing_shimmer.xml | 25 ++++++++++++++ .../main/res/layout/loading_text_shimmer.xml | 19 +++++++++++ .../res/layout/share_list_item_shimmer.xml | 14 ++------ 8 files changed, 81 insertions(+), 46 deletions(-) create mode 100644 app/src/main/res/layout/circle_shimmer.xml create mode 100644 app/src/main/res/layout/file_details_sharing_shimmer.xml create mode 100644 app/src/main/res/layout/loading_text_shimmer.xml diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java index 63daa4dec331..bb9f7e11dc47 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java @@ -32,6 +32,7 @@ import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.AnimationUtils; +import android.widget.LinearLayout; import com.nextcloud.client.account.User; import com.nextcloud.client.account.UserAccountManager; @@ -157,8 +158,12 @@ public void onCreate(@Nullable Bundle savedInstanceState) { } fileDataStorageManager = fileActivity.getStorageManager(); + fetchSharees(); + } + + private void fetchSharees() { ShareRepository shareRepository = new RemoteShareRepository(fileActivity.getClientRepository(), fileActivity, fileDataStorageManager); - shareRepository.refreshSharesForFile(file.getRemotePath(), () -> { + shareRepository.fetchSharees(file.getRemotePath(), () -> { refreshCapabilitiesFromDB(); refreshSharesFromDB(); showShareContainer(); @@ -175,8 +180,10 @@ private void showShareContainer() { return; } - binding.shimmerLayout.clearAnimation(); - binding.shimmerLayout.setVisibility(View.GONE); + final LinearLayout shimmerLayout = binding.shimmerLayout.getRoot(); + shimmerLayout.clearAnimation(); + shimmerLayout.setVisibility(View.GONE); + binding.shareContainer.setVisibility(View.VISIBLE); } @@ -185,7 +192,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, binding = FileDetailsSharingFragmentBinding.inflate(inflater, container, false); final Animation blinkAnimation = AnimationUtils.loadAnimation(requireContext(), R.anim.blink); - binding.shimmerLayout.startAnimation(blinkAnimation); + binding.shimmerLayout.getRoot().startAnimation(blinkAnimation); fileOperationsHelper = fileActivity.getFileOperationsHelper(); diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt b/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt index 051ac2895651..2a371c1bcbe7 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt @@ -25,7 +25,7 @@ class RemoteShareRepository( private val tag = "RemoteShareRepository" private val scope = lifecycleOwner.lifecycleScope - override fun refreshSharesForFile(remotePath: String, onCompleted: () -> Unit, onError: () -> Unit) { + override fun fetchSharees(remotePath: String, onCompleted: () -> Unit, onError: () -> Unit) { scope.launch(Dispatchers.IO) { val client = clientRepository.getOwncloudClient() ?: return@launch val operation = diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/share/ShareRepository.kt b/app/src/main/java/com/owncloud/android/ui/fragment/share/ShareRepository.kt index a633ea42dbab..1f34ef5f11ca 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/share/ShareRepository.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/share/ShareRepository.kt @@ -8,5 +8,5 @@ package com.owncloud.android.ui.fragment.share interface ShareRepository { - fun refreshSharesForFile(remotePath: String, onCompleted: () -> Unit, onError: () -> Unit) + fun fetchSharees(remotePath: String, onCompleted: () -> Unit, onError: () -> Unit) } diff --git a/app/src/main/res/layout/circle_shimmer.xml b/app/src/main/res/layout/circle_shimmer.xml new file mode 100644 index 000000000000..c9e0674556c4 --- /dev/null +++ b/app/src/main/res/layout/circle_shimmer.xml @@ -0,0 +1,17 @@ + + + diff --git a/app/src/main/res/layout/file_details_sharing_fragment.xml b/app/src/main/res/layout/file_details_sharing_fragment.xml index d1c2c541e526..febc93d0d0f3 100644 --- a/app/src/main/res/layout/file_details_sharing_fragment.xml +++ b/app/src/main/res/layout/file_details_sharing_fragment.xml @@ -22,6 +22,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:visibility="gone" + tools:visibility="visible" android:orientation="vertical"> - + - - - - - - - - - - - diff --git a/app/src/main/res/layout/file_details_sharing_shimmer.xml b/app/src/main/res/layout/file_details_sharing_shimmer.xml new file mode 100644 index 000000000000..f679386e4a1b --- /dev/null +++ b/app/src/main/res/layout/file_details_sharing_shimmer.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/loading_text_shimmer.xml b/app/src/main/res/layout/loading_text_shimmer.xml new file mode 100644 index 000000000000..63de923c539b --- /dev/null +++ b/app/src/main/res/layout/loading_text_shimmer.xml @@ -0,0 +1,19 @@ + + + diff --git a/app/src/main/res/layout/share_list_item_shimmer.xml b/app/src/main/res/layout/share_list_item_shimmer.xml index 4d4e8c1dd290..b1d162ecfdd7 100644 --- a/app/src/main/res/layout/share_list_item_shimmer.xml +++ b/app/src/main/res/layout/share_list_item_shimmer.xml @@ -4,8 +4,8 @@ ~ SPDX-FileCopyrightText: 2025 Alper Ozturk ~ SPDX-License-Identifier: AGPL-3.0-or-later --> - - + Date: Tue, 1 Jul 2025 14:06:45 +0200 Subject: [PATCH 24/31] fix git conflict Signed-off-by: alperozturk --- .../operations/RefreshFolderOperation.java | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java index 1bb19123c776..9e1704584193 100644 --- a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java @@ -233,7 +233,7 @@ protected RemoteOperationResult run(OwnCloudClient client) { if (mLocalFolder == null) { Log_OC.e(TAG, "Local folder is null, cannot run refresh folder operation"); - return new RemoteOperationResult(ResultCode.FILE_NOT_FOUND); + return new RemoteOperationResult<>(ResultCode.FILE_NOT_FOUND); } if (OCFile.ROOT_PATH.equals(mLocalFolder.getRemotePath()) && !mSyncFullAccount && !mOnlyFileMetadata) { @@ -263,7 +263,7 @@ protected RemoteOperationResult run(OwnCloudClient client) { fileDataStorageManager.saveFile(mLocalFolder); } else { Log_OC.e(TAG, "Local folder is null, cannot set last sync date nor save file"); - result = new RemoteOperationResult(ResultCode.FILE_NOT_FOUND); + result = new RemoteOperationResult<>(ResultCode.FILE_NOT_FOUND); } } @@ -271,8 +271,16 @@ protected RemoteOperationResult run(OwnCloudClient client) { sendLocalBroadcast(EVENT_SINGLE_FOLDER_CONTENTS_SYNCED, mLocalFolder.getRemotePath(), result); } - if (result.isSuccess() && !mSyncFullAccount && !mOnlyFileMetadata && mLocalFolder != null) { - refreshSharesForFolder(client); // share result is ignored + if (result.isSuccess() && result.getData() != null && !mSyncFullAccount && !mOnlyFileMetadata) { + final var remoteObject = result.getData(); + final ArrayList remoteFiles = new ArrayList<>(); + for (Object object: remoteObject) { + if (object instanceof RemoteFile remoteFile) { + remoteFiles.add(remoteFile); + } + } + + fileDataStorageManager.saveSharesFromRemoteFile(remoteFiles); } if (!mSyncFullAccount && mLocalFolder != null) { @@ -791,18 +799,6 @@ private void startContentSynchronizations(List filesTo } } - /** - * Syncs the Share resources for the files contained in the folder refreshed (children, not deeper descendants). - * - * - * fetch in the details only for one file - * @param client Handler of a session with an OC server. - */ - private void refreshSharesForFolder(OwnCloudClient client) { - GetSharesForFileOperation operation = new GetSharesForFileOperation(mLocalFolder.getRemotePath(), true, true, fileDataStorageManager); - operation.execute(client); - } - /** * Sends a message to any application component interested in the progress of the synchronization. * From d68bb857d877b116b40d8af4796cdbe9b80e12ea Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 18 Jul 2025 09:06:06 +0200 Subject: [PATCH 25/31] fix git conflict Signed-off-by: alperozturk --- app/src/main/res/layout/file_details_sharing_fragment.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/layout/file_details_sharing_fragment.xml b/app/src/main/res/layout/file_details_sharing_fragment.xml index febc93d0d0f3..310c49197c6e 100644 --- a/app/src/main/res/layout/file_details_sharing_fragment.xml +++ b/app/src/main/res/layout/file_details_sharing_fragment.xml @@ -40,7 +40,7 @@ android:layout_width="@dimen/user_icon_size" android:layout_height="@dimen/user_icon_size" android:contentDescription="@string/avatar" - android:src="@drawable/ic_user" /> + android:src="@drawable/ic_user_outline" /> Date: Fri, 18 Jul 2025 09:12:33 +0200 Subject: [PATCH 26/31] fix kt spotless Signed-off-by: alperozturk --- .../main/java/com/nextcloud/model/ShareeEntry.kt | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/nextcloud/model/ShareeEntry.kt b/app/src/main/java/com/nextcloud/model/ShareeEntry.kt index 327cd9b23dea..05e9a6ae1946 100644 --- a/app/src/main/java/com/nextcloud/model/ShareeEntry.kt +++ b/app/src/main/java/com/nextcloud/model/ShareeEntry.kt @@ -60,14 +60,12 @@ data class ShareeEntry( } } - private fun toContentValues(): ContentValues { - return ContentValues().apply { - put(ProviderTableMeta.OCSHARES_PATH, filePath) - put(ProviderTableMeta.OCSHARES_ACCOUNT_OWNER, accountOwner) - put(ProviderTableMeta.OCSHARES_USER_ID, fileOwnerId) - put(ProviderTableMeta.OCSHARES_SHARE_WITH_DISPLAY_NAME, shareWithDisplayName) - put(ProviderTableMeta.OCSHARES_SHARE_WITH, shareWithUserId) - put(ProviderTableMeta.OCSHARES_SHARE_TYPE, shareType) - } + private fun toContentValues(): ContentValues = ContentValues().apply { + put(ProviderTableMeta.OCSHARES_PATH, filePath) + put(ProviderTableMeta.OCSHARES_ACCOUNT_OWNER, accountOwner) + put(ProviderTableMeta.OCSHARES_USER_ID, fileOwnerId) + put(ProviderTableMeta.OCSHARES_SHARE_WITH_DISPLAY_NAME, shareWithDisplayName) + put(ProviderTableMeta.OCSHARES_SHARE_WITH, shareWithUserId) + put(ProviderTableMeta.OCSHARES_SHARE_TYPE, shareType) } } From a8f051adaab7bd560c67d7dc970c957a62e6f7c0 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 1 Aug 2025 12:20:06 +0200 Subject: [PATCH 27/31] use correct android lib version Signed-off-by: alperozturk --- build.gradle | 2 +- gradle/verification-metadata.xml | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 134b203970d7..bdd68664a29e 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ */ buildscript { ext { - androidLibraryVersion ="0303be50f9" + androidLibraryVersion ="882bc102d300faadd63044e13718aff4e2ed6f12" androidCommonLibraryVersion = "0.27.0" androidPluginVersion = "8.11.1" androidxMediaVersion = "1.5.1" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index a283aa47aa43..0b23aaeed5ff 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -85,6 +85,7 @@ + @@ -317,6 +318,7 @@ + @@ -14261,6 +14263,14 @@ + + + + + + + + @@ -17267,6 +17277,11 @@ + + + + + From d153c0766dcf097fd3ec0e2db2951d1184b0916e Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 1 Aug 2025 13:02:38 +0200 Subject: [PATCH 28/31] Rename .java to .kt Signed-off-by: alperozturk --- .../android/ui/{AvatarGroupLayout.java => AvatarGroupLayout.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/src/main/java/com/owncloud/android/ui/{AvatarGroupLayout.java => AvatarGroupLayout.kt} (100%) diff --git a/app/src/main/java/com/owncloud/android/ui/AvatarGroupLayout.java b/app/src/main/java/com/owncloud/android/ui/AvatarGroupLayout.kt similarity index 100% rename from app/src/main/java/com/owncloud/android/ui/AvatarGroupLayout.java rename to app/src/main/java/com/owncloud/android/ui/AvatarGroupLayout.kt From 30b45edbe4eed4d74b0d6ae396c56ac8008c5eda Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 1 Aug 2025 13:02:38 +0200 Subject: [PATCH 29/31] fetch updated data Signed-off-by: alperozturk --- .../owncloud/android/ui/AvatarGroupLayout.kt | 286 +++++++++--------- .../ui/fragment/OCFileListFragment.java | 4 +- 2 files changed, 147 insertions(+), 143 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/AvatarGroupLayout.kt b/app/src/main/java/com/owncloud/android/ui/AvatarGroupLayout.kt index 9eb18b558650..ae8f782f8d73 100644 --- a/app/src/main/java/com/owncloud/android/ui/AvatarGroupLayout.kt +++ b/app/src/main/java/com/owncloud/android/ui/AvatarGroupLayout.kt @@ -8,169 +8,171 @@ * * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only */ - -package com.owncloud.android.ui; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.RelativeLayout; - -import com.nextcloud.client.account.User; -import com.nextcloud.utils.GlideHelper; -import com.owncloud.android.R; -import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.lib.resources.shares.ShareeUser; -import com.owncloud.android.utils.DisplayUtils; -import com.owncloud.android.utils.theme.ViewThemeUtils; - -import java.util.List; - -import androidx.annotation.NonNull; -import androidx.annotation.Px; -import androidx.core.content.ContextCompat; -import androidx.core.content.res.ResourcesCompat; -import androidx.core.graphics.drawable.DrawableCompat; - -public class AvatarGroupLayout extends RelativeLayout implements DisplayUtils.AvatarGenerationListener { - private static final String TAG = AvatarGroupLayout.class.getSimpleName(); - - private final static int MAX_AVATAR_COUNT = 3; - - private final Drawable borderDrawable; - @Px private final int avatarSize; - @Px private final int avatarBorderSize; - @Px private final int overlapPx; - - public AvatarGroupLayout(Context context) { - this(context, null); +package com.owncloud.android.ui + +import android.content.Context +import android.content.res.Resources +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import android.widget.ImageView +import android.widget.RelativeLayout +import androidx.annotation.Px +import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat +import androidx.core.graphics.drawable.DrawableCompat +import com.nextcloud.android.common.ui.theme.utils.ColorRole +import com.nextcloud.client.account.User +import com.nextcloud.utils.GlideHelper.loadCircularBitmapIntoImageView +import com.owncloud.android.R +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.lib.resources.shares.ShareType +import com.owncloud.android.lib.resources.shares.ShareeUser +import com.owncloud.android.utils.DisplayUtils +import com.owncloud.android.utils.DisplayUtils.AvatarGenerationListener +import com.owncloud.android.utils.theme.ViewThemeUtils +import kotlin.math.min + +@Suppress("MagicNumber") +class AvatarGroupLayout @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + defStyleRes: Int = 0 +) : RelativeLayout(context, attrs, defStyleAttr, defStyleRes), + AvatarGenerationListener { + private val borderDrawable = ContextCompat.getDrawable(context, R.drawable.round_bgnd) + + @Px + private val avatarSize: Int = DisplayUtils.convertDpToPixel(40f, context) + + @Px + private val avatarBorderSize: Int = DisplayUtils.convertDpToPixel(2f, context) + + @Px + private val overlapPx: Int = DisplayUtils.convertDpToPixel(24f, context) + + init { + checkNotNull(borderDrawable) + DrawableCompat.setTint(borderDrawable, ContextCompat.getColor(context, R.color.bg_default)) } - public AvatarGroupLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } + @Suppress("LongMethod", "TooGenericExceptionCaught") + fun setAvatars(user: User, sharees: MutableList, viewThemeUtils: ViewThemeUtils) { + val context = getContext() + removeAllViews() + var avatarLayoutParams: LayoutParams? + val shareeSize = min(sharees.size, MAX_AVATAR_COUNT) + val resources = context.resources + val avatarRadius = resources.getDimension(R.dimen.list_item_avatar_icon_radius) + var sharee: ShareeUser + + var avatarCount = 0 + while (avatarCount < shareeSize) { + avatarLayoutParams = LayoutParams(avatarSize, avatarSize).apply { + setMargins(0, 0, avatarCount * overlapPx, 0) + addRule(ALIGN_PARENT_RIGHT) + } - public AvatarGroupLayout(Context context, AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } + val avatar = ImageView(context).apply { + layoutParams = avatarLayoutParams + setPadding(avatarBorderSize, avatarBorderSize, avatarBorderSize, avatarBorderSize) + background = borderDrawable + } - public AvatarGroupLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - avatarBorderSize = DisplayUtils.convertDpToPixel(2, context); - avatarSize = DisplayUtils.convertDpToPixel(40, context); - overlapPx = DisplayUtils.convertDpToPixel(24, context); - borderDrawable = ContextCompat.getDrawable(context, R.drawable.round_bgnd); - assert borderDrawable != null; - DrawableCompat.setTint(borderDrawable, ContextCompat.getColor(context, R.color.bg_default)); - } + addView(avatar) + avatar.requestLayout() - public void setAvatars(@NonNull User user, - @NonNull List sharees, - final ViewThemeUtils viewThemeUtils) { - @NonNull Context context = getContext(); - removeAllViews(); - RelativeLayout.LayoutParams avatarLayoutParams; - int avatarCount; - int shareeSize = Math.min(sharees.size(), MAX_AVATAR_COUNT); - - Resources resources = context.getResources(); - float avatarRadius = resources.getDimension(R.dimen.list_item_avatar_icon_radius); - ShareeUser sharee; - - for (avatarCount = 0; avatarCount < shareeSize; avatarCount++) { - avatarLayoutParams = new RelativeLayout.LayoutParams(avatarSize, avatarSize); - avatarLayoutParams.setMargins(0, 0, avatarCount * overlapPx, 0); - avatarLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); - - final ImageView avatar = new ImageView(context); - avatar.setLayoutParams(avatarLayoutParams); - avatar.setPadding(avatarBorderSize, avatarBorderSize, avatarBorderSize, avatarBorderSize); - - avatar.setBackground(borderDrawable); - addView(avatar); - avatar.requestLayout(); - - if (avatarCount == 0 && sharees.size() > MAX_AVATAR_COUNT) { - avatar.setImageResource(R.drawable.ic_people); - viewThemeUtils.platform.tintTextDrawable(context, avatar.getDrawable()); + if (avatarCount == 0 && sharees.size > MAX_AVATAR_COUNT) { + avatar.setImageResource(R.drawable.ic_people) + viewThemeUtils.platform.tintDrawable(context, avatar.drawable, ColorRole.ON_SURFACE) } else { - sharee = sharees.get(avatarCount); - switch (sharee.getShareType()) { - case GROUP: - case EMAIL: - case ROOM: - case CIRCLE: - viewThemeUtils.files.createAvatar(sharee.getShareType(), avatar, context); - break; - case FEDERATED: - showFederatedShareAvatar(context, - sharee.getUserId(), - avatarRadius, - resources, - avatar, - viewThemeUtils); - break; - default: - avatar.setTag(sharee); - DisplayUtils.setAvatar(user, - sharee.getUserId(), - sharee.getDisplayName(), - this, - avatarRadius, - resources, - avatar, - context); - break; + sharee = sharees[avatarCount] + when (sharee.shareType) { + ShareType.GROUP, ShareType.EMAIL, ShareType.ROOM, ShareType.CIRCLE -> + viewThemeUtils.files.createAvatar( + sharee.shareType, + avatar, + context + ) + + ShareType.FEDERATED -> showFederatedShareAvatar( + context, + sharee.userId!!, + avatarRadius, + resources, + avatar, + viewThemeUtils + ) + + else -> { + avatar.tag = sharee + DisplayUtils.setAvatar( + user, + sharee.userId!!, + sharee.displayName, + this, + avatarRadius, + resources, + avatar, + context + ) + } } } + avatarCount++ } // Recalculate container size based on avatar count - int size = overlapPx * (avatarCount - 1) + avatarSize; - ViewGroup.LayoutParams rememberParam = getLayoutParams(); - rememberParam.width = size; - setLayoutParams(rememberParam); + val size = overlapPx * (avatarCount - 1) + avatarSize + val rememberParam = layoutParams + rememberParam.width = size + layoutParams = rememberParam } - private void showFederatedShareAvatar(Context context, - String user, - float avatarRadius, - Resources resources, - ImageView avatar, - ViewThemeUtils viewThemeUtils) { + @Suppress("TooGenericExceptionCaught") + private fun showFederatedShareAvatar( + context: Context, + user: String, + avatarRadius: Float, + resources: Resources, + avatar: ImageView, + viewThemeUtils: ViewThemeUtils + ) { // maybe federated share - String[] split = user.split("@"); - String userId = split[0]; - String server = split[1]; + val split = user.split("@".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + val userId: String? = split[0] + val server = split[1] - String url = "https://" + server + "/index.php/avatar/" + userId + "/" + - resources.getInteger(R.integer.file_avatar_px); - - Drawable placeholder; + val url = "https://" + server + "/index.php/avatar/" + userId + "/" + + resources.getInteger(R.integer.file_avatar_px) + var placeholder: Drawable? try { - placeholder = TextDrawable.createAvatarByUserId(userId, avatarRadius); - } catch (Exception e) { - Log_OC.e(TAG, "Error calculating RGB value for active account icon.", e); - placeholder = viewThemeUtils.platform.colorDrawable(ResourcesCompat.getDrawable(resources, - R.drawable.account_circle_white, - null), - ContextCompat.getColor(context, R.color.black)); + placeholder = TextDrawable.createAvatarByUserId(userId, avatarRadius) + } catch (e: Exception) { + Log_OC.e(TAG, "Error calculating RGB value for active account icon.", e) + placeholder = viewThemeUtils.platform.colorDrawable( + ResourcesCompat.getDrawable( + resources, + R.drawable.account_circle_white, + null + )!!, + ContextCompat.getColor(context, R.color.black) + ) } - avatar.setTag(null); - GlideHelper.INSTANCE.loadCircularBitmapIntoImageView(context, url, avatar, placeholder); + avatar.tag = null + loadCircularBitmapIntoImageView(context, url, avatar, placeholder) } - @Override - public void avatarGenerated(Drawable avatarDrawable, Object callContext) { - ((ImageView) callContext).setImageDrawable(avatarDrawable); + override fun avatarGenerated(avatarDrawable: Drawable?, callContext: Any) { + (callContext as ImageView).setImageDrawable(avatarDrawable) } - @Override - public boolean shouldCallGeneratedCallback(String tag, Object callContext) { - return ((ImageView) callContext).getTag().equals(tag); + override fun shouldCallGeneratedCallback(tag: String?, callContext: Any): Boolean = + (callContext as ImageView).tag == tag + + companion object { + private val TAG: String = AvatarGroupLayout::class.java.simpleName + private const val MAX_AVATAR_COUNT = 3 } } 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 abe57d56409f..e05f5cd33639 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 @@ -274,7 +274,9 @@ public void onResume() { handleSearchEvent(searchEvent); } - refreshDirectory(); + if (getActivity() instanceof FileDisplayActivity fda) { + fda.startSyncFolderOperation(getCurrentFile(), true); + } super.onResume(); } From c7dded715fb8d2bb903ae34fdf65905bdac71b73 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 1 Aug 2025 13:24:27 +0200 Subject: [PATCH 30/31] since startSyncFolderOperation called onResume and startSyncFolderOperation runs on main thread it must be run on background thread thus executor is used. Signed-off-by: alperozturk --- .../ui/activity/FileDisplayActivity.java | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java index 9119cf8831bf..48feac86c6fe 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java @@ -66,7 +66,6 @@ import com.nextcloud.utils.extensions.BundleExtensionsKt; import com.nextcloud.utils.extensions.FileExtensionsKt; import com.nextcloud.utils.extensions.IntentExtensionsKt; -import com.nextcloud.utils.extensions.ViewExtensionsKt; import com.nextcloud.utils.fileNameValidator.FileNameValidator; import com.nextcloud.utils.view.FastScrollUtils; import com.owncloud.android.MainApp; @@ -146,6 +145,9 @@ import java.util.Collection; import java.util.List; import java.util.Optional; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import javax.inject.Inject; @@ -252,6 +254,8 @@ public class FileDisplayActivity extends FileActivity */ private long fileIDForImmediatePreview = -1; + private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors()); + public void setFileIDForImmediatePreview(long fileIDForImmediatePreview) { this.fileIDForImmediatePreview = fileIDForImmediatePreview; } @@ -2180,35 +2184,36 @@ public void startSyncFolderOperation(OCFile folder, boolean ignoreETag) { * @param ignoreFocus reloads file list even without focus, e.g. on tablet mode, focus can still be in detail view */ public void startSyncFolderOperation(final OCFile folder, final boolean ignoreETag, boolean ignoreFocus) { - // the execution is slightly delayed to allow the activity get the window focus if it's being started // or if the method is called from a dialog that is being dismissed - if (TextUtils.isEmpty(searchQuery) && getUser().isPresent()) { - getHandler().postDelayed(() -> { - Optional user = getUser(); - - if (!ignoreFocus && !hasWindowFocus() || !user.isPresent()) { - // do not refresh if the user rotates the device while another window has focus - // or if the current user is no longer valid - return; - } + if (!TextUtils.isEmpty(searchQuery) || getUser().isEmpty()) { + Log_OC.w(TAG,"Cannot startSyncFolderOperation, search query is empty or user not present"); + return; + } + + executor.schedule(() -> { + Optional user = getUser(); + if (!ignoreFocus && !hasWindowFocus() || user.isEmpty()) { + Log_OC.w(TAG,"do not refresh if the user rotates the device while another window has focus or if the current user is no longer valid"); + return; + } - long currentSyncTime = System.currentTimeMillis(); - mSyncInProgress = true; + long currentSyncTime = System.currentTimeMillis(); + mSyncInProgress = true; - // perform folder synchronization - RemoteOperation refreshFolderOperation = new RefreshFolderOperation(folder, currentSyncTime, false, ignoreETag, getStorageManager(), user.get(), getApplicationContext()); - refreshFolderOperation.execute(getAccount(), MainApp.getAppContext(), FileDisplayActivity.this, null, null); + // perform folder synchronization on background thread + final var refreshFolderOperation = new RefreshFolderOperation(folder, currentSyncTime, false, ignoreETag, getStorageManager(), user.get(), getApplicationContext()); + refreshFolderOperation.execute(getAccount(), MainApp.getAppContext(), FileDisplayActivity.this, null, null); + // switch back to main thread + getHandler().post(() -> { OCFileListFragment fragment = getListOfFilesFragment(); - if (fragment != null && !(fragment instanceof GalleryFragment)) { fragment.setLoading(true); } - setBackgroundText(); - }, DELAY_TO_REQUEST_REFRESH_OPERATION_LATER); - } + }); + }, DELAY_TO_REQUEST_REFRESH_OPERATION_LATER, TimeUnit.MILLISECONDS); } private void requestForDownload(OCFile file, String downloadBehaviour, String packageName, String activityName) { @@ -2600,6 +2605,7 @@ public void onReceive(Context context, Intent intent) { @Override protected void onDestroy() { + executor.shutdown(); LocalBroadcastManager.getInstance(this).unregisterReceiver(refreshFolderEventReceiver); super.onDestroy(); } From d09c82cdc53ff5851baa4195ef6c6e45c22a85e6 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 1 Aug 2025 13:32:08 +0200 Subject: [PATCH 31/31] revert Signed-off-by: alperozturk --- .../ui/activity/FileDisplayActivity.java | 52 ++++++++----------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java index 48feac86c6fe..280cb3c64d3d 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java @@ -66,6 +66,7 @@ import com.nextcloud.utils.extensions.BundleExtensionsKt; import com.nextcloud.utils.extensions.FileExtensionsKt; import com.nextcloud.utils.extensions.IntentExtensionsKt; +import com.nextcloud.utils.extensions.ViewExtensionsKt; import com.nextcloud.utils.fileNameValidator.FileNameValidator; import com.nextcloud.utils.view.FastScrollUtils; import com.owncloud.android.MainApp; @@ -145,9 +146,6 @@ import java.util.Collection; import java.util.List; import java.util.Optional; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; import javax.inject.Inject; @@ -254,8 +252,6 @@ public class FileDisplayActivity extends FileActivity */ private long fileIDForImmediatePreview = -1; - private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors()); - public void setFileIDForImmediatePreview(long fileIDForImmediatePreview) { this.fileIDForImmediatePreview = fileIDForImmediatePreview; } @@ -443,19 +439,19 @@ private void checkOutdatedServer() { DisplayUtils.showServerOutdatedSnackbar(this, Snackbar.LENGTH_LONG); } } - + private void checkNotifications() { new Thread(() -> { try { RemoteOperationResult> result = new GetNotificationsRemoteOperation() .execute(clientFactory.createNextcloudClient(accountManager.getUser())); - + if (result.isSuccess() && !result.getResultData().isEmpty()) { runOnUiThread(() -> mNotificationButton.setVisibility(View.VISIBLE)); } else { runOnUiThread(() -> mNotificationButton.setVisibility(View.GONE)); } - + } catch (ClientFactory.CreationException e) { Log_OC.e(TAG, "Could not fetch notifications!"); } @@ -2184,36 +2180,35 @@ public void startSyncFolderOperation(OCFile folder, boolean ignoreETag) { * @param ignoreFocus reloads file list even without focus, e.g. on tablet mode, focus can still be in detail view */ public void startSyncFolderOperation(final OCFile folder, final boolean ignoreETag, boolean ignoreFocus) { + // the execution is slightly delayed to allow the activity get the window focus if it's being started // or if the method is called from a dialog that is being dismissed - if (!TextUtils.isEmpty(searchQuery) || getUser().isEmpty()) { - Log_OC.w(TAG,"Cannot startSyncFolderOperation, search query is empty or user not present"); - return; - } - - executor.schedule(() -> { - Optional user = getUser(); - if (!ignoreFocus && !hasWindowFocus() || user.isEmpty()) { - Log_OC.w(TAG,"do not refresh if the user rotates the device while another window has focus or if the current user is no longer valid"); - return; - } + if (TextUtils.isEmpty(searchQuery) && getUser().isPresent()) { + getHandler().postDelayed(() -> { + Optional user = getUser(); + + if (!ignoreFocus && !hasWindowFocus() || !user.isPresent()) { + // do not refresh if the user rotates the device while another window has focus + // or if the current user is no longer valid + return; + } - long currentSyncTime = System.currentTimeMillis(); - mSyncInProgress = true; + long currentSyncTime = System.currentTimeMillis(); + mSyncInProgress = true; - // perform folder synchronization on background thread - final var refreshFolderOperation = new RefreshFolderOperation(folder, currentSyncTime, false, ignoreETag, getStorageManager(), user.get(), getApplicationContext()); - refreshFolderOperation.execute(getAccount(), MainApp.getAppContext(), FileDisplayActivity.this, null, null); + // perform folder synchronization + RemoteOperation refreshFolderOperation = new RefreshFolderOperation(folder, currentSyncTime, false, ignoreETag, getStorageManager(), user.get(), getApplicationContext()); + refreshFolderOperation.execute(getAccount(), MainApp.getAppContext(), FileDisplayActivity.this, null, null); - // switch back to main thread - getHandler().post(() -> { OCFileListFragment fragment = getListOfFilesFragment(); + if (fragment != null && !(fragment instanceof GalleryFragment)) { fragment.setLoading(true); } + setBackgroundText(); - }); - }, DELAY_TO_REQUEST_REFRESH_OPERATION_LATER, TimeUnit.MILLISECONDS); + }, DELAY_TO_REQUEST_REFRESH_OPERATION_LATER); + } } private void requestForDownload(OCFile file, String downloadBehaviour, String packageName, String activityName) { @@ -2605,7 +2600,6 @@ public void onReceive(Context context, Intent intent) { @Override protected void onDestroy() { - executor.shutdown(); LocalBroadcastManager.getInstance(this).unregisterReceiver(refreshFolderEventReceiver); super.onDestroy(); }