mirror of
https://github.com/nextcloud/android.git
synced 2024-11-26 23:28:42 +03:00
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:
parent
0736d01f01
commit
5b8d11f738
12 changed files with 484 additions and 89 deletions
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue