Optimize loading time for Shared view (#10015)

* Don't load entire OCFiles in the Shared view, as it takes too long

Instead, build partially-filled OCFiles from the OCShares we get

Signed-off-by: Álvaro Brey Vilas <alvaro.brey@nextcloud.com>

* Load metadata of shared files only when attempting to interact with them

This was quite a big undertaking and required a whole new fragment

Signed-off-by: Álvaro Brey Vilas <alvaro.brey@nextcloud.com>

* Fix wrong usages of deprecaed OCShare shareType setter

Signed-off-by: Álvaro Brey Vilas <alvaro.brey@nextcloud.com>
This commit is contained in:
Álvaro Brey 2022-04-04 18:52:21 +02:00 committed by GitHub
parent 0736d01f01
commit 5b8d11f738
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 484 additions and 89 deletions

View file

@ -79,6 +79,7 @@ import com.owncloud.android.ui.fragment.FileDetailSharingFragment;
import com.owncloud.android.ui.fragment.GalleryFragment;
import com.owncloud.android.ui.fragment.LocalFileListFragment;
import com.owncloud.android.ui.fragment.OCFileListFragment;
import com.owncloud.android.ui.fragment.SharedListFragment;
import com.owncloud.android.ui.fragment.UnifiedSearchFragment;
import com.owncloud.android.ui.fragment.contactsbackup.BackupFragment;
import com.owncloud.android.ui.fragment.contactsbackup.BackupListFragment;
@ -215,4 +216,7 @@ abstract class ComponentsModule {
@ContributesAndroidInjector
abstract PreviewPdfFragment previewPDFFragment();
@ContributesAndroidInjector
abstract SharedListFragment sharedFragment();
}

View file

@ -68,6 +68,7 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
* synchronization of THE CONTENTS of this file.
*/
private long modificationTimestampAtLastSyncForData;
private long firstShareTimestamp; // UNIX timestamp of the first share time
private String remotePath;
private String decryptedRemotePath;
private String localPath;
@ -160,6 +161,7 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
mountType = (WebdavEntry.MountType) source.readSerializable();
richWorkspace = source.readString();
previewAvailable = source.readInt() == 1;
firstShareTimestamp = source.readLong();
}
@Override
@ -193,6 +195,7 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
dest.writeSerializable(mountType);
dest.writeString(richWorkspace);
dest.writeInt(previewAvailable ? 1 : 0);
dest.writeLong(firstShareTimestamp);
}
public void setDecryptedRemotePath(String path) {
@ -455,6 +458,7 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
encrypted = false;
mountType = WebdavEntry.MountType.INTERNAL;
richWorkspace = "";
firstShareTimestamp = 0;
}
/**
@ -819,4 +823,12 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
public void setRichWorkspace(String richWorkspace) {
this.richWorkspace = richWorkspace;
}
public long getFirstShareTimestamp() {
return firstShareTimestamp;
}
public void setFirstShareTimestamp(long firstShareTimestamp) {
this.firstShareTimestamp = firstShareTimestamp;
}
}

View file

@ -101,6 +101,7 @@ import com.owncloud.android.ui.events.SearchEvent;
import com.owncloud.android.ui.fragment.FileDetailsSharingProcessFragment;
import com.owncloud.android.ui.fragment.GalleryFragment;
import com.owncloud.android.ui.fragment.OCFileListFragment;
import com.owncloud.android.ui.fragment.SharedListFragment;
import com.owncloud.android.ui.preview.PreviewTextStringFragment;
import com.owncloud.android.ui.trashbin.TrashbinActivity;
import com.owncloud.android.utils.BitmapUtils;
@ -431,6 +432,7 @@ public abstract class DrawerActivity extends ToolbarActivity
if (itemId == R.id.nav_all_files) {
if (this instanceof FileDisplayActivity &&
!(((FileDisplayActivity) this).getLeftFragment() instanceof GalleryFragment) &&
!(((FileDisplayActivity) this).getLeftFragment() instanceof SharedListFragment) &&
!(((FileDisplayActivity) this).getLeftFragment() instanceof PreviewTextStringFragment)) {
showFiles(false);
((FileDisplayActivity) this).browseToRoot();
@ -470,8 +472,7 @@ public abstract class DrawerActivity extends ToolbarActivity
UserInfoActivity.openAccountRemovalConfirmationDialog(optionalUser.get(), getSupportFragmentManager());
}
} else if (itemId == R.id.nav_shared) {
handleSearchEvents(new SearchEvent("", SearchRemoteOperation.SearchType.SHARED_FILTER),
menuItem.getItemId());
startSharedSearch(menuItem);
} else if (itemId == R.id.nav_recently_modified) {
handleSearchEvents(new SearchEvent("", SearchRemoteOperation.SearchType.RECENTLY_MODIFIED_SEARCH),
menuItem.getItemId());
@ -519,6 +520,13 @@ public abstract class DrawerActivity extends ToolbarActivity
}
}
private void startSharedSearch(MenuItem menuItem) {
SearchEvent searchEvent = new SearchEvent("", SearchRemoteOperation.SearchType.SHARED_FILTER);
MainApp.showOnlyFilesOnDevice(false);
launchActivityForSearch(searchEvent, menuItem.getItemId());
}
private void startPhotoSearch(MenuItem menuItem) {
SearchEvent searchEvent = new SearchEvent("image/%", SearchRemoteOperation.SearchType.PHOTO_SEARCH);
MainApp.showOnlyFilesOnDevice(false);
@ -528,7 +536,8 @@ public abstract class DrawerActivity extends ToolbarActivity
private void handleSearchEvents(SearchEvent searchEvent, int menuItemId) {
if (this instanceof FileDisplayActivity) {
if (((FileDisplayActivity) this).getLeftFragment() instanceof GalleryFragment) {
final Fragment leftFragment = ((FileDisplayActivity) this).getLeftFragment();
if (leftFragment instanceof GalleryFragment || leftFragment instanceof SharedListFragment) {
launchActivityForSearch(searchEvent, menuItemId);
} else {
EventBus.getDefault().post(searchEvent);

View file

@ -100,6 +100,7 @@ import com.owncloud.android.ui.fragment.FileFragment;
import com.owncloud.android.ui.fragment.GalleryFragment;
import com.owncloud.android.ui.fragment.OCFileListFragment;
import com.owncloud.android.ui.fragment.SearchType;
import com.owncloud.android.ui.fragment.SharedListFragment;
import com.owncloud.android.ui.fragment.TaskRetainerFragment;
import com.owncloud.android.ui.fragment.UnifiedSearchFragment;
import com.owncloud.android.ui.helpers.FileOperationsHelper;
@ -492,6 +493,13 @@ public class FileDisplayActivity extends FileActivity
bundle.putParcelable(OCFileListFragment.SEARCH_EVENT, searchEvent);
photoFragment.setArguments(bundle);
setLeftFragment(photoFragment);
} else if (searchEvent.getSearchType().equals(SearchRemoteOperation.SearchType.SHARED_FILTER)) {
Log_OC.d(this, "Switch to shared fragment");
SharedListFragment sharedListFragment = new SharedListFragment();
Bundle bundle = new Bundle();
bundle.putParcelable(OCFileListFragment.SEARCH_EVENT, searchEvent);
sharedListFragment.setArguments(bundle);
setLeftFragment(sharedListFragment);
} else {
Log_OC.d(this, "Switch to oc file search fragment");
@ -2287,8 +2295,10 @@ public class FileDisplayActivity extends FileActivity
public void onMessageEvent(final SearchEvent event) {
if (SearchRemoteOperation.SearchType.PHOTO_SEARCH == event.getSearchType()) {
Log_OC.d(this, "Switch to photo search fragment");
setLeftFragment(new GalleryFragment());
} else if (event.getSearchType() == SearchRemoteOperation.SearchType.SHARED_FILTER) {
Log_OC.d(this, "Switch to Shared fragment");
setLeftFragment(new SharedListFragment());
}
}

View file

@ -27,7 +27,6 @@ import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
@ -73,11 +72,6 @@ public abstract class ToolbarActivity extends BaseActivity {
protected AppCompatSpinner mToolbarSpinner;
private boolean isHomeSearchToolbarShow = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
/**
* Toolbar setup that must be called in implementer's {@link #onCreate} after {@link #setContentView} if they want
* to use the toolbar.

View file

@ -63,9 +63,7 @@ import com.owncloud.android.db.ProviderMeta;
import com.owncloud.android.files.services.FileDownloader;
import com.owncloud.android.files.services.FileUploader;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation;
import com.owncloud.android.lib.resources.files.model.RemoteFile;
import com.owncloud.android.lib.resources.shares.OCShare;
import com.owncloud.android.lib.resources.shares.ShareType;
@ -148,7 +146,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
OCFileListFragmentInterface ocFileListFragmentInterface,
boolean argHideItemOptions,
boolean gridView
) {
) {
this.ocFileListFragmentInterface = ocFileListFragmentInterface;
this.activity = activity;
this.preferences = preferences;
@ -162,7 +160,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
if (this.user != null) {
AccountManager platformAccountManager = AccountManager.get(this.activity);
userId = platformAccountManager.getUserData(this.user.toPlatformAccount(),
com.owncloud.android.lib.common.accounts.AccountUtils.Constants.KEY_USER_ID);
com.owncloud.android.lib.common.accounts.AccountUtils.Constants.KEY_USER_ID);
} else {
userId = "";
}
@ -219,7 +217,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
if (removeFromList) {
mFiles.remove(file);
}
break;
}
}
@ -386,10 +384,10 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
if (highlightedItem != null && file.getFileId() == highlightedItem.getFileId()) {
gridViewHolder.getItemLayout().setBackgroundColor(activity.getResources()
.getColor(R.color.selected_item_background));
.getColor(R.color.selected_item_background));
} else if (isCheckedFile(file)) {
gridViewHolder.getItemLayout().setBackgroundColor(activity.getResources()
.getColor(R.color.selected_item_background));
.getColor(R.color.selected_item_background));
gridViewHolder.getCheckbox().setImageDrawable(
ThemeDrawableUtils.tintDrawable(R.drawable.ic_checkbox_marked,
ThemeColorUtils.primaryColor(activity)));
@ -403,7 +401,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
if (!hideItemOptions) {
gridViewHolder.getItemLayout().setLongClickable(true);
gridViewHolder.getItemLayout().setOnLongClickListener(v ->
ocFileListFragmentInterface.onLongItemClicked(file));
ocFileListFragmentInterface.onLongItemClicked(file));
}
// unread comments
@ -458,11 +456,34 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
}
itemViewHolder.getFileSize().setText(DisplayUtils.bytesToHumanReadable(localSize));
itemViewHolder.getFileSize().setVisibility(View.VISIBLE);
itemViewHolder.getFileSizeSeparator().setVisibility(View.VISIBLE);
} else {
itemViewHolder.getFileSize().setText(DisplayUtils.bytesToHumanReadable(file.getFileLength()));
final long fileLength = file.getFileLength();
if (fileLength >= 0) {
itemViewHolder.getFileSize().setText(DisplayUtils.bytesToHumanReadable(fileLength));
itemViewHolder.getFileSize().setVisibility(View.VISIBLE);
itemViewHolder.getFileSizeSeparator().setVisibility(View.VISIBLE);
} else {
itemViewHolder.getFileSize().setVisibility(View.GONE);
itemViewHolder.getFileSizeSeparator().setVisibility(View.GONE);
}
}
itemViewHolder.getLastModification().setText(DisplayUtils.getRelativeTimestamp(activity,
file.getModificationTimestamp()));
final long modificationTimestamp = file.getModificationTimestamp();
if (modificationTimestamp > 0) {
itemViewHolder.getLastModification().setText(DisplayUtils.getRelativeTimestamp(activity,
modificationTimestamp));
itemViewHolder.getLastModification().setVisibility(View.VISIBLE);
} else if (file.getFirstShareTimestamp() > 0) {
itemViewHolder.getLastModification().setText(
DisplayUtils.getRelativeTimestamp(activity, file.getFirstShareTimestamp())
);
itemViewHolder.getLastModification().setVisibility(View.VISIBLE);
} else {
itemViewHolder.getLastModification().setVisibility(View.GONE);
}
if (multiSelect || gridView || hideItemOptions) {
itemViewHolder.getOverflowMenu().setVisibility(View.GONE);
@ -505,6 +526,9 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
}
gridViewHolder.getFavorite().setVisibility(file.isFavorite() ? View.VISIBLE : View.GONE);
} else {
gridViewHolder.getLocalFileIndicator().setVisibility(View.GONE);
gridViewHolder.getFavorite().setVisibility(View.GONE);
}
if (multiSelect) {
@ -567,7 +591,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
// Thumbnail in cache?
Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(
ThumbnailsCacheManager.PREFIX_THUMBNAIL + file.getRemoteId()
);
);
if (thumbnail != null && !file.isUpdateThumbnailNeeded()) {
stopShimmer(shimmerThumbnail, thumbnailView);
@ -655,7 +679,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
public void onViewAttachedToWindow(@NonNull RecyclerView.ViewHolder holder) {
if (holder instanceof ListGridImageViewHolder) {
LoaderImageView thumbnailShimmer = ((ListGridImageViewHolder) holder).getShimmerThumbnail();
if (thumbnailShimmer.getVisibility() == View.VISIBLE){
if (thumbnailShimmer.getVisibility() == View.VISIBLE) {
thumbnailShimmer.setImageResource(R.drawable.background);
thumbnailShimmer.resetLoader();
}
@ -834,7 +858,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
OCFile directory,
FileDataStorageManager updatedStorageManager,
boolean onlyOnDevice, String limitToMimeType
) {
) {
this.onlyOnDevice = onlyOnDevice;
if (updatedStorageManager != null && !updatedStorageManager.equals(mStorageManager)) {
@ -913,12 +937,12 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
}
}
if (searchType != SearchType.GALLERY_SEARCH &&
searchType != SearchType.RECENTLY_MODIFIED_SEARCH) {
if (searchType == SearchType.GALLERY_SEARCH ||
searchType == SearchType.RECENTLY_MODIFIED_SEARCH) {
mFiles = FileStorageUtils.sortOcFolderDescDateModifiedWithoutFavoritesFirst(mFiles);
} else if (searchType != SearchType.SHARED_FILTER) {
FileSortOrder sortOrder = preferences.getSortOrderByFolder(folder);
mFiles = sortOrder.sortCloudFiles(mFiles);
} else {
mFiles = FileStorageUtils.sortOcFolderDescDateModifiedWithoutFavoritesFirst(mFiles);
}
mFilesAll.clear();
@ -934,41 +958,12 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
// check type before cast as of long running data fetch it is possible that old result is filled
if (shareObject instanceof OCShare) {
OCShare ocShare = (OCShare) shareObject;
shares.add(ocShare);
// get ocFile from Server to have an up-to-date copy
RemoteOperationResult result = new ReadFileRemoteOperation(ocShare.getPath()).execute(user.toPlatformAccount(),
activity);
if (result.isSuccess()) {
OCFile file = FileStorageUtils.fillOCFile((RemoteFile) result.getData().get(0));
FileStorageUtils.searchForLocalFileInDefaultPath(file, user.getAccountName());
file = mStorageManager.saveFileWithParent(file, activity);
ShareType newShareType = ocShare.getShareType();
if (newShareType == ShareType.PUBLIC_LINK) {
file.setSharedViaLink(true);
} else if (newShareType == ShareType.USER ||
newShareType == ShareType.GROUP ||
newShareType == ShareType.EMAIL ||
newShareType == ShareType.FEDERATED ||
newShareType == ShareType.ROOM ||
newShareType == ShareType.CIRCLE) {
file.setSharedWithSharee(true);
}
mStorageManager.saveFile(file);
if (!mFiles.contains(file)) {
mFiles.add(file);
}
} else {
Log_OC.e(TAG, "Error in getting prop for file: " + ocShare.getPath());
}
}
}
List<OCFile> files = OCShareToOCFileConverter.buildOCFilesFromShares(shares);
mFiles.addAll(files);
mStorageManager.saveShares(shares);
}
@ -1224,7 +1219,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
currentDirectory = folder;
}
static class OCFileListItemViewHolder extends RecyclerView.ViewHolder implements ListItemViewHolder{
static class OCFileListItemViewHolder extends RecyclerView.ViewHolder implements ListItemViewHolder {
protected ListItemBinding binding;
private OCFileListItemViewHolder(ListItemBinding binding) {
@ -1238,6 +1233,11 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
return binding.fileSize;
}
@Override
public View getFileSizeSeparator() {
return binding.fileSeparator;
}
@Override
public TextView getLastModification() {
return binding.lastMod;
@ -1447,6 +1447,8 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
interface ListItemViewHolder extends ListGridItemViewHolder {
TextView getFileSize();
View getFileSizeSeparator();
TextView getLastModification();
ImageView getOverflowMenu();

View file

@ -0,0 +1,77 @@
/*
* Nextcloud Android client application
*
* @author Álvaro Brey Vilas
* Copyright (C) 2022 Álvaro Brey Vilas
* Copyright (C) 2022 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.owncloud.android.ui.adapter
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.lib.resources.shares.OCShare
import com.owncloud.android.lib.resources.shares.ShareType
import com.owncloud.android.lib.resources.shares.ShareeUser
object OCShareToOCFileConverter {
private const val MILLIS_PER_SECOND = 1000
/**
* Generates a list of incomplete [OCFile] from a list of [OCShare]
*
* This is actually pretty complex as we get one [OCShare] item for each shared instance for the same folder
*
* **THIS ONLY WORKS WITH FILES SHARED *BY* THE USER, NOT FOR SHARES *WITH* THE USER**
*/
@JvmStatic
fun buildOCFilesFromShares(shares: List<OCShare>): List<OCFile> {
val groupedByPath: Map<String, List<OCShare>> = shares.groupBy { it.path }
return groupedByPath
.map { (path: String, shares: List<OCShare>) -> buildOcFile(path, shares) }
.sortedByDescending { it.firstShareTimestamp }
}
private fun buildOcFile(path: String, shares: List<OCShare>): OCFile {
require(shares.all { it.path == path })
// common attributes
val firstShare = shares.first()
val file = OCFile(path).apply {
decryptedRemotePath = path
ownerId = firstShare.userId
ownerDisplayName = firstShare.ownerDisplayName
isPreviewAvailable = firstShare.isHasPreview
mimeType = firstShare.mimetype
note = firstShare.note
fileId = firstShare.fileSource
remoteId = firstShare.remoteId.toString()
// use first share timestamp as timestamp
firstShareTimestamp = shares.minOf { it.sharedDate * MILLIS_PER_SECOND }
// don't have file length or mod timestamp
fileLength = -1
modificationTimestamp = -1
}
if (shares.any { it.shareType in listOf(ShareType.PUBLIC_LINK, ShareType.EMAIL) }) {
file.isSharedViaLink = true
}
if (shares.any { it.shareType !in listOf(ShareType.PUBLIC_LINK, ShareType.EMAIL) }) {
file.isSharedWithSharee = true
file.sharees = shares
.filter { it.shareType != ShareType.PUBLIC_LINK && it.shareType != ShareType.EMAIL }
.map { ShareeUser(it.shareWith, it.sharedWithDisplayName, it.shareType) }
}
return file
}
}

View file

@ -196,7 +196,9 @@ public class ShareeListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
shares.addAll(users);
// add internal share link at end
shares.add(new OCShare().setShareType(ShareType.INTERNAL));
final OCShare ocShare = new OCShare();
ocShare.setShareType(ShareType.INTERNAL);
shares.add(ocShare);
}
public List<OCShare> getShares() {

View file

@ -410,7 +410,9 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda
if (publicShares.isEmpty() && containsNoNewPublicShare(adapter.getShares())) {
publicShares.add(new OCShare().setShareType(ShareType.NEW_PUBLIC_LINK));
final OCShare ocShare = new OCShare();
ocShare.setShareType(ShareType.NEW_PUBLIC_LINK);
publicShares.add(ocShare);
} else {
adapter.removeNewPublicShare();
}

View file

@ -71,7 +71,6 @@ import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.e2ee.ToggleEncryptionRemoteOperation;
import com.owncloud.android.lib.resources.files.SearchRemoteOperation;
import com.owncloud.android.lib.resources.files.ToggleFavoriteRemoteOperation;
import com.owncloud.android.lib.resources.shares.GetSharesRemoteOperation;
import com.owncloud.android.lib.resources.status.OCCapability;
import com.owncloud.android.ui.activity.FileActivity;
import com.owncloud.android.ui.activity.FileDisplayActivity;
@ -1509,7 +1508,7 @@ public class OCFileListFragment extends ExtendedListFragment implements
handleSearchEvent(event);
}
private void handleSearchEvent(SearchEvent event) {
protected void handleSearchEvent(SearchEvent event) {
if (SearchRemoteOperation.SearchType.PHOTO_SEARCH == event.getSearchType()) {
return;
}
@ -1537,30 +1536,29 @@ public class OCFileListFragment extends ExtendedListFragment implements
final User currentUser = accountManager.getUser();
final RemoteOperation remoteOperation;
if (currentSearchType != SearchType.SHARED_FILTER) {
boolean searchOnlyFolders = false;
if (getArguments() != null && getArguments().getBoolean(ARG_SEARCH_ONLY_FOLDER, false)) {
searchOnlyFolders = true;
}
OCCapability ocCapability = mContainerActivity.getStorageManager()
.getCapability(currentUser.getAccountName());
remoteOperation = new SearchRemoteOperation(event.getSearchQuery(),
event.getSearchType(),
searchOnlyFolders,
ocCapability);
} else {
remoteOperation = new GetSharesRemoteOperation();
}
final RemoteOperation remoteOperation = getSearchRemoteOperation(currentUser, event);
remoteOperationAsyncTask = new OCFileListSearchAsyncTask(mContainerActivity, this, remoteOperation, currentUser, event);
remoteOperationAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
protected RemoteOperation getSearchRemoteOperation(final User currentUser, final SearchEvent event) {
boolean searchOnlyFolders = false;
if (getArguments() != null && getArguments().getBoolean(ARG_SEARCH_ONLY_FOLDER, false)) {
searchOnlyFolders = true;
}
OCCapability ocCapability = mContainerActivity.getStorageManager()
.getCapability(currentUser.getAccountName());
return new SearchRemoteOperation(event.getSearchQuery(),
event.getSearchType(),
searchOnlyFolders,
ocCapability);
}
@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onMessageEvent(EncryptionEvent event) {
final User user = accountManager.getUser();
@ -1609,12 +1607,7 @@ public class OCFileListFragment extends ExtendedListFragment implements
}
protected void setTitle(@StringRes final int title) {
getActivity().runOnUiThread(() -> {
if (getActivity() != null && ((FileDisplayActivity) getActivity()).getSupportActionBar() != null) {
ThemeToolbarUtils.setColoredTitle(((FileDisplayActivity) getActivity()).getSupportActionBar(),
title, getContext());
}
});
setTitle(getContext().getString(title));
}
protected void setTitle(final String title) {
@ -1698,7 +1691,6 @@ public class OCFileListFragment extends ExtendedListFragment implements
}
SearchRemoteOperation.SearchType searchType = event.getSearchType();
return !TextUtils.isEmpty(event.getSearchQuery()) ||
searchType == SearchRemoteOperation.SearchType.SHARED_SEARCH ||
searchType == SearchRemoteOperation.SearchType.SHARED_FILTER ||
searchType == SearchRemoteOperation.SearchType.FAVORITE_SEARCH ||
searchType == SearchRemoteOperation.SearchType.RECENTLY_MODIFIED_SEARCH;

View file

@ -0,0 +1,162 @@
/*
* Nextcloud Android client application
*
* @author Tobias Kaminsky
* Copyright (C) 2019 Tobias Kaminsky
* Copyright (C) 2019 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.owncloud.android.ui.fragment
import android.os.Bundle
import android.os.Handler
import android.view.View
import androidx.lifecycle.lifecycleScope
import com.nextcloud.client.account.User
import com.nextcloud.client.di.Injectable
import com.nextcloud.client.logger.Logger
import com.owncloud.android.R
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation
import com.owncloud.android.lib.resources.files.SearchRemoteOperation
import com.owncloud.android.lib.resources.files.model.RemoteFile
import com.owncloud.android.lib.resources.shares.GetSharesRemoteOperation
import com.owncloud.android.ui.activity.FileDisplayActivity
import com.owncloud.android.ui.events.SearchEvent
import com.owncloud.android.utils.DisplayUtils
import com.owncloud.android.utils.FileStorageUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
/**
* A Fragment that lists folders shared by the user
*/
@Suppress("TooManyFunctions")
class SharedListFragment : OCFileListFragment(), Injectable {
@Inject
lateinit var logger: Logger
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
searchFragment = true
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
mAdapter.setShowMetadata(false)
currentSearchType = SearchType.SHARED_FILTER
searchEvent = SearchEvent("", SearchRemoteOperation.SearchType.SHARED_FILTER)
menuItemAddRemoveValue = MenuItemAddRemove.REMOVE_GRID_AND_SORT
requireActivity().invalidateOptionsMenu()
}
override fun onResume() {
super.onResume()
Handler().post {
if (activity is FileDisplayActivity) {
val fileDisplayActivity = activity as FileDisplayActivity
fileDisplayActivity.updateActionBarTitleAndHomeButtonByString(getString(R.string.drawer_item_shared))
fileDisplayActivity.setMainFabVisible(false)
}
}
}
override fun getSearchRemoteOperation(currentUser: User?, event: SearchEvent?): RemoteOperation<*> {
return GetSharesRemoteOperation()
}
private suspend fun fetchFileData(partialFile: OCFile): OCFile? {
return withContext(Dispatchers.IO) {
val user = accountManager.user
val fetchResult = ReadFileRemoteOperation(partialFile.remotePath)
.execute(user.toPlatformAccount(), context)
if (!fetchResult.isSuccess) {
logger.e(SHARED_TAG, "Error fetching file")
if (fetchResult.isException) {
logger.e(SHARED_TAG, "exception: ", fetchResult.exception)
}
null
} else {
val remoteFile = fetchResult.data[0] as RemoteFile
val file = FileStorageUtils.fillOCFile(remoteFile)
FileStorageUtils.searchForLocalFileInDefaultPath(file, user.accountName)
val savedFile = mContainerActivity.storageManager.saveFileWithParent(file, context)
savedFile.apply {
isSharedViaLink = partialFile.isSharedViaLink
isSharedWithSharee = partialFile.isSharedWithSharee
sharees = partialFile.sharees
}
}
}
}
private fun fetchFileAndRun(partialFile: OCFile, block: (file: OCFile) -> Unit) {
lifecycleScope.launch {
isLoading = true
val file = fetchFileData(partialFile)
isLoading = false
if (file != null) {
block(file)
} else {
DisplayUtils.showSnackMessage(requireActivity(), R.string.error_retrieving_file)
}
}
}
override fun onShareIconClick(file: OCFile) {
fetchFileAndRun(file) { fetched ->
super.onShareIconClick(fetched)
}
}
override fun showShareDetailView(file: OCFile) {
fetchFileAndRun(file) { fetched ->
super.showShareDetailView(fetched)
}
}
override fun showActivityDetailView(file: OCFile) {
fetchFileAndRun(file) { fetched ->
super.showActivityDetailView(fetched)
}
}
override fun onOverflowIconClicked(file: OCFile, view: View?) {
fetchFileAndRun(file) { fetched ->
super.onOverflowIconClicked(fetched, view)
}
}
override fun onItemClicked(file: OCFile) {
fetchFileAndRun(file) { fetched ->
super.onItemClicked(fetched)
}
}
override fun onLongItemClicked(file: OCFile): Boolean {
fetchFileAndRun(file) { fetched ->
super.onLongItemClicked(fetched)
}
return true
}
companion object {
private val SHARED_TAG = SharedListFragment::class.java.simpleName
}
}

View file

@ -0,0 +1,129 @@
/*
* Nextcloud Android client application
*
* @author Álvaro Brey Vilas
* Copyright (C) 2022 Álvaro Brey Vilas
* Copyright (C) 2022 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.owncloud.android.ui.adapter
import com.owncloud.android.lib.resources.shares.OCShare
import com.owncloud.android.lib.resources.shares.ShareType
import org.junit.Assert
import org.junit.Test
class OCShareToOCFileConverterTest {
@Test
fun testSingleOCShare() {
val shares = listOf(
OCShare("/foo")
.apply {
shareType = ShareType.PUBLIC_LINK
}
)
val result = OCShareToOCFileConverter.buildOCFilesFromShares(shares)
Assert.assertEquals("Wrong file list size", 1, result.size)
val ocFile = result[0]
Assert.assertEquals("Wrong file path", "/foo", ocFile.remotePath)
Assert.assertEquals("File should have link attribute", true, ocFile.isSharedViaLink)
Assert.assertEquals("File should not have sharee attribute", false, ocFile.isSharedWithSharee)
}
@Test
fun testMultipleSharesSamePath() {
val shares = listOf(
OCShare("/foo")
.apply {
shareType = ShareType.PUBLIC_LINK
sharedDate = 10
},
OCShare("/foo")
.apply {
shareType = ShareType.EMAIL
sharedDate = 22
},
OCShare("/foo")
.apply {
shareType = ShareType.INTERNAL
sharedDate = 11
shareWith = "abcd"
sharedWithDisplayName = "Ab Cd"
}
)
val result = OCShareToOCFileConverter.buildOCFilesFromShares(shares)
Assert.assertEquals("Wrong file list size", 1, result.size)
val ocFile = result[0]
Assert.assertEquals("Wrong file path", "/foo", ocFile.remotePath)
Assert.assertEquals("File should have link attribute", true, ocFile.isSharedViaLink)
Assert.assertEquals("File should have sharee attribute", true, ocFile.isSharedWithSharee)
Assert.assertEquals("Wrong name of sharees", 1, ocFile.sharees.size)
Assert.assertEquals("Wrong shared timestamp", 10000, ocFile.firstShareTimestamp)
}
@Test
fun testMultipleSharesMultiplePaths() {
val shares = listOf(
OCShare("/foo")
.apply {
shareType = ShareType.INTERNAL
sharedDate = 10
shareWith = "aabc"
sharedWithDisplayName = "Aa Bc"
},
OCShare("/foo")
.apply {
shareType = ShareType.INTERNAL
sharedDate = 22
shareWith = "cccc"
sharedWithDisplayName = "Cc Cc"
},
OCShare("/foo")
.apply {
shareType = ShareType.INTERNAL
sharedDate = 11
shareWith = "abcd"
sharedWithDisplayName = "Ab Cd"
},
OCShare("/bar")
.apply {
shareType = ShareType.EMAIL
sharedDate = 5
},
)
val result = OCShareToOCFileConverter.buildOCFilesFromShares(shares)
Assert.assertEquals("Wrong file list size", 2, result.size)
val ocFile = result[0]
Assert.assertEquals("Wrong file path", "/foo", ocFile.remotePath)
Assert.assertEquals("File should have no link attribute", false, ocFile.isSharedViaLink)
Assert.assertEquals("File should have sharee attribute", true, ocFile.isSharedWithSharee)
Assert.assertEquals("Wrong name of sharees", 3, ocFile.sharees.size)
Assert.assertEquals("Wrong shared timestamp", 10000, ocFile.firstShareTimestamp)
val ocFile2 = result[1]
Assert.assertEquals("Wrong file path", "/bar", ocFile2.remotePath)
Assert.assertEquals("File should have link attribute", true, ocFile2.isSharedViaLink)
Assert.assertEquals("File should have no sharee attribute", false, ocFile2.isSharedWithSharee)
}
}