From 0eef5fb5fc88af855930198587a6459aab92cb7a Mon Sep 17 00:00:00 2001 From: alperozturk Date: Wed, 11 Jun 2025 11:39:54 +0200 Subject: [PATCH 01/32] 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 5e93e1db652f..be8bebe752d7 100644 --- a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java @@ -784,6 +784,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 67f04db6ac5370c6f6bfc0a72892937d8af5a001 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Wed, 11 Jun 2025 12:34:33 +0200 Subject: [PATCH 02/32] 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 56d90ec8684f647c87c7a50b0d2df87d386a1b6c Mon Sep 17 00:00:00 2001 From: alperozturk Date: Wed, 11 Jun 2025 13:46:17 +0200 Subject: [PATCH 03/32] 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 42b34d441fa3ac4f6193053ae5a05971d606f0fc Mon Sep 17 00:00:00 2001 From: alperozturk Date: Wed, 11 Jun 2025 14:08:34 +0200 Subject: [PATCH 04/32] 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 46911e05c0da42187e4ccfe14b32c250b0763535 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Wed, 11 Jun 2025 14:35:15 +0200 Subject: [PATCH 05/32] 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 49f6b9068d5348dcf02bc53f40ecfb620c7ff903 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Thu, 12 Jun 2025 10:06:49 +0200 Subject: [PATCH 06/32] 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 98b8d9b2779c..bd68a7767797 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -12268,6 +12268,14 @@ + + + + + + + + From d8ede2d73d2aa7a858bebcd37f578340ae15c54d Mon Sep 17 00:00:00 2001 From: alperozturk Date: Thu, 12 Jun 2025 10:11:55 +0200 Subject: [PATCH 07/32] 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 948550335e391de007999275b28b0cce293fe64d Mon Sep 17 00:00:00 2001 From: alperozturk Date: Thu, 12 Jun 2025 10:19:00 +0200 Subject: [PATCH 08/32] 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 ed23af6f42b1cf92f009ef00437d9c192b38818d Mon Sep 17 00:00:00 2001 From: alperozturk Date: Thu, 12 Jun 2025 15:38:33 +0200 Subject: [PATCH 09/32] 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 d5263d698adffb3e7a30a016fc80f4dd3562b946 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Thu, 12 Jun 2025 15:41:07 +0200 Subject: [PATCH 10/32] 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 79e047ad969b68acdf18a42ed79c5a25bb6ac7a2 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 13 Jun 2025 09:15:57 +0200 Subject: [PATCH 11/32] 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 21e194e66e36acbd3917cba0b005eb4c2f706656 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 13 Jun 2025 09:29:53 +0200 Subject: [PATCH 12/32] 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 8b308481704ef07039a50e5bfc6514e1a356a2b7 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 13 Jun 2025 09:51:22 +0200 Subject: [PATCH 13/32] 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 ebc9e12a9d3a..c609d312de5c 100644 --- a/app/src/main/java/com/owncloud/android/utils/DisplayUtils.java +++ b/app/src/main/java/com/owncloud/android/utils/DisplayUtils.java @@ -486,30 +486,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 06e34c78aab80a037bbf786351e55b4c3ddda15f Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 13 Jun 2025 10:40:46 +0200 Subject: [PATCH 14/32] 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 042438430fc255ff62a2eacffc63bec064f33ec0 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 13 Jun 2025 10:40:56 +0200 Subject: [PATCH 15/32] 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 ea389adfc570bb2d0130d87e1eb4291fc9a007c2 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 13 Jun 2025 11:05:04 +0200 Subject: [PATCH 16/32] 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 d3ed142e61287360124ba9403e6a77f88c5a6846 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 13 Jun 2025 12:10:49 +0200 Subject: [PATCH 17/32] 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 c0c6b6723ce6a9eca9ba263a357f0799d36e2d8d Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 13 Jun 2025 13:32:26 +0200 Subject: [PATCH 18/32] 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 057b3692ab83..397e56612dc8 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 896662b721f6f50805918ff294aeb8ca2f30f45e Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 13 Jun 2025 13:45:30 +0200 Subject: [PATCH 19/32] add shimmering Signed-off-by: alperozturk --- .../fragment/FileDetailSharingFragment.java | 4 + .../layout/file_details_sharing_fragment.xml | 305 ++++++++++-------- 2 files changed, 180 insertions(+), 129 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 adb3a65bf77e..53ffc42cd683 100644 --- a/app/src/main/res/layout/file_details_sharing_fragment.xml +++ b/app/src/main/res/layout/file_details_sharing_fragment.xml @@ -12,14 +12,13 @@ android:layout_height="match_parent" android:paddingTop="@dimen/standard_eight_padding"> - + android:layout_height="match_parent" + android:layout_below="@id/appbar"> - + - - + android:paddingRight="@dimen/standard_padding"> + android:text="@string/shared_with_you_by" + android:textSize="@dimen/two_line_primary_text_size" /> + + + + + + + + + + + + + + + + + - - + - + - + + - - + android:text="@string/show_all" /> - + - + - + + + - + - + + - - - + android:layout_height="match_parent"> - - - + - + + + + + + + + + + + + + + - - From c1729bff9f7c01cad052c4ec42ec47dc189255d2 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 13 Jun 2025 13:57:44 +0200 Subject: [PATCH 20/32] 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 53ffc42cd683..76ac3d5448dd 100644 --- a/app/src/main/res/layout/file_details_sharing_fragment.xml +++ b/app/src/main/res/layout/file_details_sharing_fragment.xml @@ -206,34 +206,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 083545a3beed8b4cd85ff048a3c328feb18bf341 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 13 Jun 2025 14:04:03 +0200 Subject: [PATCH 21/32] 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 1d4e855a97e4711a5719f246f7879938824a9322 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 13 Jun 2025 14:05:28 +0200 Subject: [PATCH 22/32] 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 ca8a5ee0e1d9ed164c1e14f41f23a1e1a598d465 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 13 Jun 2025 15:49:37 +0200 Subject: [PATCH 23/32] 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 | 32 ++----------------- .../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, 80 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 76ac3d5448dd..642725c19a91 100644 --- a/app/src/main/res/layout/file_details_sharing_fragment.xml +++ b/app/src/main/res/layout/file_details_sharing_fragment.xml @@ -63,7 +63,6 @@ android:id="@+id/shared_with_you_username" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="@string/shared_with_you_by" android:textSize="@dimen/two_line_primary_text_size" /> - - - - - - - + - - - - - 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/32] fix git conflict Signed-off-by: alperozturk --- .../android/operations/RefreshFolderOperation.java | 12 ------------ 1 file changed, 12 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 be8bebe752d7..69046088a92b 100644 --- a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java @@ -781,18 +781,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 e1a3d578f4b7e809a407ce53a64fd69328b8c6e4 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 18 Jul 2025 09:06:06 +0200 Subject: [PATCH 25/32] 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 642725c19a91..1430f0afc61c 100644 --- a/app/src/main/res/layout/file_details_sharing_fragment.xml +++ b/app/src/main/res/layout/file_details_sharing_fragment.xml @@ -49,7 +49,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/32] 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 f66d6dcd393954083c30eba7cdbf57d933cb1ea8 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 1 Aug 2025 12:20:06 +0200 Subject: [PATCH 27/32] use correct android lib version Signed-off-by: alperozturk --- gradle/verification-metadata.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index bd68a7767797..af3386de5cab 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -75,6 +75,7 @@ + @@ -295,6 +296,7 @@ + @@ -15068,6 +15070,11 @@ + + + + + From 9877d0b94a5ddbf467e9a59f0ecd63d98ec3c471 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 1 Aug 2025 13:02:38 +0200 Subject: [PATCH 28/32] 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 4edd7088e37f4badc8a8826767f42f42f58631c9 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 1 Aug 2025 13:02:38 +0200 Subject: [PATCH 29/32] fetch updated data Signed-off-by: alperozturk --- .../owncloud/android/ui/AvatarGroupLayout.kt | 240 +++++++++--------- .../ui/fragment/OCFileListFragment.java | 4 +- 2 files changed, 127 insertions(+), 117 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 91dbd0f3f35b..03b29a0ce8e3 100644 --- a/app/src/main/java/com/owncloud/android/ui/AvatarGroupLayout.kt +++ b/app/src/main/java/com/owncloud/android/ui/AvatarGroupLayout.kt @@ -8,8 +8,29 @@ * * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only */ - -package com.owncloud.android.ui; +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 import android.content.Context; import android.content.res.Resources; @@ -29,7 +50,8 @@ 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; + @Px + private val avatarBorderSize: Int = DisplayUtils.convertDpToPixel(2f, context) import androidx.annotation.NonNull; import androidx.annotation.Px; @@ -39,129 +61,113 @@ import androidx.core.graphics.drawable.DrawableCompat; import androidx.core.graphics.drawable.RoundedBitmapDrawable; import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory; -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); + 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); @@ -180,13 +186,15 @@ public class AvatarGroupLayout extends RelativeLayout implements DisplayUtils.Av }); } - @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 85c3bf0e4c75..eac91ed8934f 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 @@ -277,7 +277,9 @@ public void onResume() { handleSearchEvent(searchEvent); } - refreshDirectory(); + if (getActivity() instanceof FileDisplayActivity fda) { + fda.startSyncFolderOperation(getCurrentFile(), true); + } super.onResume(); } From 0ebead78b20a5bc38632d16a2e46ed6237b3fb73 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 1 Aug 2025 13:24:27 +0200 Subject: [PATCH 30/32] 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 [skip ci] --- .../ui/activity/FileDisplayActivity.java | 44 ++++++++++--------- 1 file changed, 24 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 d4b066f9b81d..4cb6aa140b6f 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; @@ -2105,35 +2107,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; + } - long currentSyncTime = System.currentTimeMillis(); - mSyncInProgress = true; + 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; + } - // perform folder synchronization - RemoteOperation refreshFolderOperation = new RefreshFolderOperation(folder, currentSyncTime, false, ignoreETag, getStorageManager(), user.get(), getApplicationContext()); - refreshFolderOperation.execute(getAccount(), MainApp.getAppContext(), FileDisplayActivity.this, null, null); + long currentSyncTime = System.currentTimeMillis(); + mSyncInProgress = true; - OCFileListFragment fragment = getListOfFilesFragment(); + // 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) { @@ -2520,6 +2523,7 @@ public void onReceive(Context context, Intent intent) { @Override protected void onDestroy() { + executor.shutdown(); LocalBroadcastManager.getInstance(this).unregisterReceiver(refreshFolderEventReceiver); super.onDestroy(); } From 52db9159ec02082763b80bbb20c4d11ad18b4a60 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Mon, 4 Aug 2025 09:37:22 +0200 Subject: [PATCH 31/32] fix build Signed-off-by: alperozturk --- .../operations/RefreshFolderOperation.java | 30 +++++++-- .../owncloud/android/ui/AvatarGroupLayout.kt | 61 ++++++++----------- .../ui/activity/FileDisplayActivity.java | 43 ++++++------- .../layout/file_details_sharing_fragment.xml | 17 ++---- 4 files changed, 74 insertions(+), 77 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 69046088a92b..9e1704584193 100644 --- a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java @@ -231,6 +231,11 @@ protected RemoteOperationResult run(OwnCloudClient client) { mConflictsFound = 0; mForgottenLocalFiles.clear(); + if (mLocalFolder == null) { + Log_OC.e(TAG, "Local folder is null, cannot run refresh folder operation"); + return new RemoteOperationResult<>(ResultCode.FILE_NOT_FOUND); + } + if (OCFile.ROOT_PATH.equals(mLocalFolder.getRemotePath()) && !mSyncFullAccount && !mOnlyFileMetadata) { updateOCVersion(client); updateUserProfile(); @@ -253,19 +258,32 @@ protected RemoteOperationResult run(OwnCloudClient client) { mLocalFolder.setEtag(""); } - mLocalFolder.setLastSyncDateForData(System.currentTimeMillis()); - fileDataStorageManager.saveFile(mLocalFolder); + if (mLocalFolder != null) { + mLocalFolder.setLastSyncDateForData(System.currentTimeMillis()); + 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); + } } - if (!mSyncFullAccount && mRemoteFolderChanged) { + if (!mSyncFullAccount && mRemoteFolderChanged && mLocalFolder != null) { sendLocalBroadcast(EVENT_SINGLE_FOLDER_CONTENTS_SYNCED, mLocalFolder.getRemotePath(), result); } - if (result.isSuccess() && !mSyncFullAccount && !mOnlyFileMetadata) { - 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) { + if (!mSyncFullAccount && mLocalFolder != null) { sendLocalBroadcast(EVENT_SINGLE_FOLDER_SHARES_SYNCED, mLocalFolder.getRemotePath(), result); } 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 03b29a0ce8e3..82bf5ae6bbb9 100644 --- a/app/src/main/java/com/owncloud/android/ui/AvatarGroupLayout.kt +++ b/app/src/main/java/com/owncloud/android/ui/AvatarGroupLayout.kt @@ -12,6 +12,7 @@ package com.owncloud.android.ui import android.content.Context import android.content.res.Resources +import android.graphics.Bitmap import android.graphics.drawable.Drawable import android.util.AttributeSet import android.widget.ImageView @@ -20,9 +21,11 @@ import androidx.annotation.Px import androidx.core.content.ContextCompat import androidx.core.content.res.ResourcesCompat import androidx.core.graphics.drawable.DrawableCompat +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.bumptech.glide.Glide +import com.bumptech.glide.request.target.BitmapImageViewTarget 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 @@ -32,34 +35,24 @@ import com.owncloud.android.utils.DisplayUtils.AvatarGenerationListener import com.owncloud.android.utils.theme.ViewThemeUtils import kotlin.math.min -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.RelativeLayout; - -import com.bumptech.glide.Glide; -import com.bumptech.glide.request.target.BitmapImageViewTarget; -import com.nextcloud.client.account.User; -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; +@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) -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; -import androidx.core.graphics.drawable.RoundedBitmapDrawable; -import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory; + @Px + private val overlapPx: Int = DisplayUtils.convertDpToPixel(24f, context) init { checkNotNull(borderDrawable) @@ -170,20 +163,20 @@ import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory; ) } - avatar.setTag(null); + avatar.tag = null Glide.with(context).load(url) .asBitmap() .placeholder(placeholder) .error(placeholder) - .into(new BitmapImageViewTarget(avatar) { - @Override - protected void setResource(Bitmap resource) { - RoundedBitmapDrawable circularBitmapDrawable = RoundedBitmapDrawableFactory.create(resources, - resource); - circularBitmapDrawable.setCircular(true); - avatar.setImageDrawable(circularBitmapDrawable); + .into(object : BitmapImageViewTarget(avatar) { + override fun setResource(resource: Bitmap?) { + resource?.let { + val circularBitmapDrawable = RoundedBitmapDrawableFactory.create(resources, it) + circularBitmapDrawable.isCircular = true + avatar.setImageDrawable(circularBitmapDrawable) + } } - }); + }) } override fun avatarGenerated(avatarDrawable: Drawable?, callContext: Any) { 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 4cb6aa140b6f..095cb4fe14bd 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 @@ -145,9 +145,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; @@ -2107,36 +2104,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; - } + 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; + } - 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, TimeUnit.MILLISECONDS); + }, DELAY_TO_REQUEST_REFRESH_OPERATION_LATER); + } } private void requestForDownload(OCFile file, String downloadBehaviour, String packageName, String activityName) { @@ -2523,7 +2519,6 @@ public void onReceive(Context context, Intent intent) { @Override protected void onDestroy() { - executor.shutdown(); LocalBroadcastManager.getInstance(this).unregisterReceiver(refreshFolderEventReceiver); super.onDestroy(); } 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 1430f0afc61c..febc93d0d0f3 100644 --- a/app/src/main/res/layout/file_details_sharing_fragment.xml +++ b/app/src/main/res/layout/file_details_sharing_fragment.xml @@ -21,18 +21,9 @@ android:id="@+id/shareContainer" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/standard_half_margin" - android:orientation="horizontal" - android:paddingLeft="@dimen/standard_padding" - android:paddingTop="@dimen/standard_padding" - android:paddingRight="@dimen/standard_padding"> - - + android:visibility="gone" + tools:visibility="visible" + android:orientation="vertical"> + android:src="@drawable/ic_user" /> Date: Mon, 4 Aug 2025 09:38:13 +0200 Subject: [PATCH 32/32] fix kt spotless Signed-off-by: alperozturk --- .../android/ui/fragment/FileDetailsSharingProcessFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt index 807accfdbb43..1b7c812ef204 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt @@ -166,7 +166,7 @@ class FileDetailsSharingProcessFragment : permission = share?.permissions ?: capabilities.defaultPermissions - ?: SharePermissionManager.getMaximumPermission(isFolder()) + ?: SharePermissionManager.getMaximumPermission(isFolder()) } private fun initArguments() {