diff --git a/drawable_resources/ic_people.svg b/drawable_resources/ic_people.svg new file mode 100644 index 0000000000..a03024bde6 --- /dev/null +++ b/drawable_resources/ic_people.svg @@ -0,0 +1,5 @@ + + + + diff --git a/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java b/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java index 7112e814fc..0936688660 100644 --- a/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -209,6 +209,7 @@ public class FileDataStorageManager { cv.put(ProviderTableMeta.FILE_OWNER_ID, file.getOwnerId()); cv.put(ProviderTableMeta.FILE_OWNER_DISPLAY_NAME, file.getOwnerDisplayName()); cv.put(ProviderTableMeta.FILE_NOTE, file.getNote()); + cv.put(ProviderTableMeta.FILE_SHAREES, TextUtils.join(",", file.getSharees())); boolean sameRemotePath = fileExists(file.getRemotePath()); if (sameRemotePath || @@ -452,6 +453,7 @@ public class FileDataStorageManager { cv.put(ProviderTableMeta.FILE_OWNER_ID, folder.getOwnerId()); cv.put(ProviderTableMeta.FILE_OWNER_DISPLAY_NAME, folder.getOwnerDisplayName()); cv.put(ProviderTableMeta.FILE_NOTE, folder.getNote()); + cv.put(ProviderTableMeta.FILE_SHAREES, TextUtils.join(",", folder.getSharees())); return cv; } @@ -491,6 +493,7 @@ public class FileDataStorageManager { cv.put(ProviderTableMeta.FILE_OWNER_ID, file.getOwnerId()); cv.put(ProviderTableMeta.FILE_OWNER_DISPLAY_NAME, file.getOwnerDisplayName()); cv.put(ProviderTableMeta.FILE_NOTE, file.getNote()); + cv.put(ProviderTableMeta.FILE_SHAREES, TextUtils.join(",", file.getSharees())); return cv; } @@ -989,6 +992,14 @@ public class FileDataStorageManager { file.setOwnerId(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_OWNER_ID))); file.setOwnerDisplayName(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_OWNER_DISPLAY_NAME))); file.setNote(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_NOTE))); + + String sharees = c.getString(c.getColumnIndex(ProviderTableMeta.FILE_SHAREES)); + + if (sharees == null || sharees.isEmpty()) { + file.setSharees(new ArrayList<>()); + } else { + file.setSharees(new ArrayList<>(Arrays.asList(sharees.split(",")))); + } } return file; @@ -1217,7 +1228,7 @@ public class FileDataStorageManager { share.setSharedWithDisplayName( c.getString(c.getColumnIndex(ProviderTableMeta.OCSHARES_SHARE_WITH_DISPLAY_NAME))); share.setFolder(c.getInt(c.getColumnIndex(ProviderTableMeta.OCSHARES_IS_DIRECTORY)) == 1); - share.setUserId(c.getLong(c.getColumnIndex(ProviderTableMeta.OCSHARES_USER_ID))); + share.setUserId(c.getString(c.getColumnIndex(ProviderTableMeta.OCSHARES_USER_ID))); share.setRemoteId(c.getLong(c.getColumnIndex(ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED))); share.setPasswordProtected(c.getInt(c.getColumnIndex(ProviderTableMeta.OCSHARES_IS_PASSWORD_PROTECTED)) == 1); share.setNote(c.getString(c.getColumnIndex(ProviderTableMeta.OCSHARES_NOTE))); diff --git a/src/main/java/com/owncloud/android/datamodel/OCFile.java b/src/main/java/com/owncloud/android/datamodel/OCFile.java index f7e7c9dc4f..dbb5d7972e 100644 --- a/src/main/java/com/owncloud/android/datamodel/OCFile.java +++ b/src/main/java/com/owncloud/android/datamodel/OCFile.java @@ -28,22 +28,22 @@ import android.content.Context; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; - +import androidx.annotation.NonNull; +import androidx.core.content.FileProvider; import com.owncloud.android.R; import com.owncloud.android.lib.common.network.WebdavEntry; import com.owncloud.android.lib.common.network.WebdavUtils; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.lib.resources.files.model.ServerFileInterface; import com.owncloud.android.utils.MimeType; - -import java.io.File; - -import androidx.annotation.NonNull; -import androidx.core.content.FileProvider; import lombok.Getter; import lombok.Setter; import third_parties.daveKoeller.AlphanumComparator; +import java.io.File; +import java.util.ArrayList; +import java.util.List; + public class OCFile implements Parcelable, Comparable, ServerFileInterface { private final static String PERMISSION_SHARED_WITH_ME = "S"; private final static String PERMISSION_CAN_RESHARE = "R"; @@ -88,6 +88,7 @@ public class OCFile implements Parcelable, Comparable, ServerFileInterfa @Getter @Setter private String ownerId; @Getter @Setter private String ownerDisplayName; @Getter @Setter String note; + @Getter @Setter private List sharees = new ArrayList<>(); /** * URI to the local path of the file contents, if stored in the device; cached after first call diff --git a/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java b/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java index 03e2bd5845..4ec98d15f3 100644 --- a/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java +++ b/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java @@ -44,6 +44,7 @@ import android.view.Display; import android.view.MenuItem; import android.view.WindowManager; import android.widget.ImageView; + import com.owncloud.android.MainApp; import com.owncloud.android.R; import com.owncloud.android.lib.common.OwnCloudAccount; @@ -62,7 +63,7 @@ import com.owncloud.android.utils.ConnectivityUtils; import com.owncloud.android.utils.DisplayUtils.AvatarGenerationListener; import com.owncloud.android.utils.FileStorageUtils; import com.owncloud.android.utils.MimeTypeUtil; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.methods.GetMethod; import org.jetbrains.annotations.NotNull; @@ -74,6 +75,8 @@ import java.lang.ref.WeakReference; import java.net.URLEncoder; import java.util.List; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + /** * Manager for concurrent access to thumbnails cache. */ diff --git a/src/main/java/com/owncloud/android/db/ProviderMeta.java b/src/main/java/com/owncloud/android/db/ProviderMeta.java index 81a9f3f655..22031b01d1 100644 --- a/src/main/java/com/owncloud/android/db/ProviderMeta.java +++ b/src/main/java/com/owncloud/android/db/ProviderMeta.java @@ -31,7 +31,7 @@ import com.owncloud.android.MainApp; */ public class ProviderMeta { public static final String DB_NAME = "filelist"; - public static final int DB_VERSION = 44; + public static final int DB_VERSION = 45; private ProviderMeta() { // No instance @@ -109,6 +109,7 @@ public class ProviderMeta { public static final String FILE_OWNER_ID = "owner_id"; public static final String FILE_OWNER_DISPLAY_NAME = "owner_display_name"; public static final String FILE_NOTE = "note"; + public static final String FILE_SHAREES = "sharees"; public static final String[] FILE_ALL_COLUMNS = { _ID, FILE_PARENT, FILE_NAME, FILE_CREATION, FILE_MODIFIED, @@ -116,7 +117,7 @@ public class ProviderMeta { FILE_PATH, FILE_ACCOUNT_OWNER, FILE_LAST_SYNC_DATE, FILE_LAST_SYNC_DATE_FOR_DATA, FILE_ETAG, FILE_ETAG_ON_SERVER, FILE_SHARED_VIA_LINK, FILE_SHARED_WITH_SHAREE, FILE_PUBLIC_LINK, FILE_PERMISSIONS, FILE_REMOTE_ID, FILE_UPDATE_THUMBNAIL, FILE_IS_DOWNLOADING, FILE_ETAG_IN_CONFLICT, FILE_FAVORITE, - FILE_IS_ENCRYPTED, FILE_MOUNT_TYPE, FILE_HAS_PREVIEW, FILE_UNREAD_COMMENTS_COUNT + FILE_IS_ENCRYPTED, FILE_MOUNT_TYPE, FILE_HAS_PREVIEW, FILE_UNREAD_COMMENTS_COUNT, FILE_SHAREES }; public static final String FILE_DEFAULT_SORT_ORDER = FILE_NAME + " collate nocase asc"; diff --git a/src/main/java/com/owncloud/android/providers/FileContentProvider.java b/src/main/java/com/owncloud/android/providers/FileContentProvider.java index 4649e6458d..29562bc22a 100644 --- a/src/main/java/com/owncloud/android/providers/FileContentProvider.java +++ b/src/main/java/com/owncloud/android/providers/FileContentProvider.java @@ -701,7 +701,8 @@ public class FileContentProvider extends ContentProvider { + ProviderTableMeta.FILE_UNREAD_COMMENTS_COUNT + INTEGER + ProviderTableMeta.FILE_OWNER_ID + TEXT + ProviderTableMeta.FILE_OWNER_DISPLAY_NAME + TEXT - + ProviderTableMeta.FILE_NOTE + " TEXT);" + + ProviderTableMeta.FILE_NOTE + TEXT + + ProviderTableMeta.FILE_SHAREES + " TEXT);" ); } @@ -1874,9 +1875,9 @@ public class FileContentProvider extends ContentProvider { db.beginTransaction(); try { db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME + - ADD_COLUMN + ProviderTableMeta.FILE_OWNER_ID + " TEXT "); + ADD_COLUMN + ProviderTableMeta.FILE_OWNER_ID + " TEXT "); db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME + - ADD_COLUMN + ProviderTableMeta.FILE_OWNER_DISPLAY_NAME + " TEXT "); + ADD_COLUMN + ProviderTableMeta.FILE_OWNER_DISPLAY_NAME + " TEXT "); upgraded = true; db.setTransactionSuccessful(); @@ -1906,6 +1907,24 @@ public class FileContentProvider extends ContentProvider { if (!upgraded) { Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion)); } + + if (oldVersion < 45 && newVersion >= 45) { + Log_OC.i(SQL, "Entering in the #45 add sharees to file table"); + db.beginTransaction(); + try { + db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME + + ADD_COLUMN + ProviderTableMeta.FILE_SHAREES + " TEXT "); + + upgraded = true; + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + + if (!upgraded) { + Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion)); + } } @Override diff --git a/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java index a52f7c235b..38d6d9ee86 100644 --- a/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java +++ b/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java @@ -25,6 +25,7 @@ package com.owncloud.android.ui.adapter; import android.accounts.Account; +import android.accounts.AccountManager; import android.content.ContentValues; import android.content.Context; import android.content.res.Resources; @@ -40,6 +41,7 @@ import android.view.ViewGroup; import android.widget.Filter; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.RelativeLayout; import android.widget.TextView; import com.bumptech.glide.Glide; @@ -77,6 +79,7 @@ import com.owncloud.android.utils.ThemeUtils; import java.io.File; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Locale; @@ -100,6 +103,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter mFiles = new ArrayList<>(); @@ -123,6 +127,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter asyncTasks = new ArrayList<>(); private boolean onlyOnDevice; + private boolean showShareAvatar; public OCFileListAdapter( Context context, @@ -146,6 +151,17 @@ public class OCFileListAdapter extends RecyclerView.Adapter sharees = (ArrayList) file.getSharees(); - float avatarRadius = resources.getDimension(R.dimen.list_item_avatar_icon_radius); - - if (file.getOwnerId().contains("@")) { - showFederatedShareAvatar(file, avatarRadius, resources, itemViewHolder); - } else { - itemViewHolder.sharedAvatar.setTag(file.getOwnerId()); - DisplayUtils.setAvatar(account, file.getOwnerId(), this, avatarRadius, resources, - itemViewHolder.sharedAvatar, mContext); + // use fileOwner if not oneself, then add at first + if (!fileOwner.equals(userId) && !sharees.contains(fileOwner)) { + sharees.add(fileOwner); } - itemViewHolder.sharedAvatar.setOnClickListener(view -> - ocFileListFragmentInterface.showShareDetailView(file)); + Collections.reverse(sharees); + + Log_OC.d(this, "sharees of " + file.getFileName() + ": " + sharees); + + int shareeSize = Math.min(sharees.size(), 3); + int w = DisplayUtils.convertDpToPixel(40, mContext); + int margin = DisplayUtils.convertDpToPixel(24, mContext); + int size = 60 * (shareeSize - 1) + w; + + for (int i = 0; i < shareeSize; i++) { + String sharee = file.getSharees().get(i); + + ImageView avatar = new ImageView(mContext); + + if (i == 0 && sharees.size() > 3) { + avatar.setImageResource(R.drawable.ic_people); + } else { + if (sharee.contains("@")) { + showFederatedShareAvatar(sharee, avatarRadius, resources, avatar); + } else { + avatar.setTag(sharee); + DisplayUtils.setAvatar(account, sharee, this, avatarRadius, resources, + avatar, mContext); + Log_OC.d(TAG, "avatar: " + sharee); + } + } + + avatar.setOnClickListener(view -> ocFileListFragmentInterface.showShareDetailView(file)); + + RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(w, w); + layoutParams.setMargins(0, 0, i * margin, 0); + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); + avatar.setLayoutParams(layoutParams); + itemViewHolder.sharedAvatars.addView(avatar); + + ViewGroup.LayoutParams rememberParam = itemViewHolder.sharedAvatars.getLayoutParams(); + rememberParam.width = size; + + itemViewHolder.sharedAvatars.setLayoutParams(rememberParam); + } } else { - itemViewHolder.sharedAvatar.setVisibility(View.GONE); + itemViewHolder.sharedAvatars.setVisibility(View.GONE); + itemViewHolder.sharedAvatars.removeAllViews(); } if (onlyOnDevice) { @@ -435,13 +490,13 @@ public class OCFileListAdapter extends RecyclerView.Adapter shares; private float avatarRadiusDimension; private OCFile file; + private String userId; public UserListAdapter(FragmentManager fragmentManager, Context context, List shares, Account account, - OCFile file, ShareeListAdapterListener listener) { + OCFile file, ShareeListAdapterListener listener, String userId) { this.context = context; this.fragmentManager = fragmentManager; this.shares = shares; this.listener = listener; this.file = file; + this.userId = userId; accentColor = ThemeUtils.primaryAccentColor(context); capabilities = new FileDataStorageManager(account, context.getContentResolver()).getCapability(account.name); @@ -120,12 +120,20 @@ public class UserListAdapter extends RecyclerView.Adapter allowEditClick(holder.allowEditing, share)); + if (share.getShareWith().equalsIgnoreCase(userId) || share.getUserId().equalsIgnoreCase(userId)) { + holder.allowEditing.setVisibility(View.VISIBLE); + holder.editShareButton.setVisibility(View.VISIBLE); - // bind listener to edit privileges - holder.editShareButton.setOnClickListener(v -> onOverflowIconClicked(v, holder.allowEditing, share)); + ThemeUtils.tintCheckbox(holder.allowEditing, accentColor); + holder.allowEditing.setChecked(canEdit(share)); + holder.allowEditing.setOnClickListener(v -> allowEditClick(holder.allowEditing, share)); + + // bind listener to edit privileges + holder.editShareButton.setOnClickListener(v -> onOverflowIconClicked(v, holder.allowEditing, share)); + } else { + holder.allowEditing.setVisibility(View.GONE); + holder.editShareButton.setVisibility(View.GONE); + } } } diff --git a/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java b/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java index e6b21394f2..75aeee9fa4 100644 --- a/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java +++ b/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java @@ -39,12 +39,7 @@ import android.widget.LinearLayout; import android.widget.PopupMenu; import android.widget.ProgressBar; import android.widget.TextView; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.viewpager.widget.ViewPager; -import butterknife.BindView; -import butterknife.ButterKnife; -import butterknife.Unbinder; + import com.google.android.material.tabs.TabLayout; import com.nextcloud.client.di.Injectable; import com.nextcloud.client.preferences.AppPreferences; @@ -68,9 +63,17 @@ import com.owncloud.android.utils.DisplayUtils; import com.owncloud.android.utils.MimeTypeUtil; import com.owncloud.android.utils.ThemeUtils; -import javax.inject.Inject; import java.lang.ref.WeakReference; +import javax.inject.Inject; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.viewpager.widget.ViewPager; +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.Unbinder; + /** * This Fragment is used to display the details about a file. */ diff --git a/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java b/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java index 76b968cdbb..38a0dba2e3 100644 --- a/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java +++ b/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java @@ -21,6 +21,7 @@ package com.owncloud.android.ui.fragment; import android.accounts.Account; +import android.accounts.AccountManager; import android.app.SearchManager; import android.content.Context; import android.content.res.Resources; @@ -36,7 +37,17 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.PopupMenu; import android.widget.TextView; - +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatCheckBox; +import androidx.appcompat.widget.SearchView; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.Unbinder; import com.google.android.material.snackbar.Snackbar; import com.owncloud.android.R; import com.owncloud.android.authentication.AccountUtils; @@ -63,18 +74,6 @@ import com.owncloud.android.utils.ThemeUtils; import java.util.List; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.widget.AppCompatCheckBox; -import androidx.appcompat.widget.SearchView; -import androidx.fragment.app.Fragment; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import butterknife.BindView; -import butterknife.ButterKnife; -import butterknife.OnClick; -import butterknife.Unbinder; - public class FileDetailSharingFragment extends Fragment implements UserListAdapter.ShareeListAdapterListener, DisplayUtils.AvatarGenerationListener { @@ -222,6 +221,7 @@ public class FileDetailSharingFragment extends Fragment implements UserListAdapt ThemeUtils.themeSearchView(getContext(), searchView, false); } else { searchView.setVisibility(View.GONE); + shareByLinkContainer.setVisibility(View.GONE); noList.setText(R.string.reshare_not_allowed); } } @@ -296,9 +296,13 @@ public class FileDetailSharingFragment extends Fragment implements UserListAdapt // TODO Refactoring: create a new {@link ShareUserListAdapter} instance with every call should not be needed if (shares.size() > 0) { + AccountManager accountManager = AccountManager.get(getContext()); + String userId = accountManager.getUserData(account, + com.owncloud.android.lib.common.accounts.AccountUtils.Constants.KEY_USER_ID); + usersList.setVisibility(View.VISIBLE); - usersList.setAdapter(new UserListAdapter(fileDisplayActivity.getSupportFragmentManager(), fileDisplayActivity, shares, - account, file, this)); + usersList.setAdapter(new UserListAdapter(fileDisplayActivity.getSupportFragmentManager(), + fileDisplayActivity, shares, account, file, this, userId)); usersList.setLayoutManager(new LinearLayoutManager(getContext())); usersList.addItemDecoration(new SimpleListItemDividerDecoration(getContext())); noList.setVisibility(View.GONE); diff --git a/src/main/java/com/owncloud/android/utils/FileStorageUtils.java b/src/main/java/com/owncloud/android/utils/FileStorageUtils.java index b51119321d..47085f3106 100644 --- a/src/main/java/com/owncloud/android/utils/FileStorageUtils.java +++ b/src/main/java/com/owncloud/android/utils/FileStorageUtils.java @@ -210,6 +210,7 @@ public final class FileStorageUtils { file.setOwnerId(remote.getOwnerId()); file.setOwnerDisplayName(remote.getOwnerDisplayName()); file.setNote(remote.getNote()); + file.setSharees(remote.getSharees()); return file; } diff --git a/src/main/res/drawable/ic_people.xml b/src/main/res/drawable/ic_people.xml new file mode 100644 index 0000000000..c7f2c78afb --- /dev/null +++ b/src/main/res/drawable/ic_people.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/main/res/layout/list_item.xml b/src/main/res/layout/list_item.xml index b9d67284e2..d03e57ed82 100644 --- a/src/main/res/layout/list_item.xml +++ b/src/main/res/layout/list_item.xml @@ -17,14 +17,14 @@ along with this program. If not, see . --> + android:id="@+id/ListItemLayout" + android:layout_width="match_parent" + android:layout_height="@dimen/standard_list_item_size" + android:background="@drawable/list_selector" + android:descendantFocusability="blocksDescendants" + android:foreground="?android:attr/selectableItemBackground" + android:baselineAligned="false" + android:orientation="horizontal"> + android:src="@drawable/folder"/> - + @@ -90,7 +90,7 @@ android:singleLine="true" android:text="@string/placeholder_filename" android:textColor="@color/textColor" - android:textSize="@dimen/two_line_primary_text_size" /> + android:textSize="@dimen/two_line_primary_text_size"/> + android:textSize="@dimen/two_line_secondary_text_size"/> + android:textSize="@dimen/two_line_secondary_text_size"/> + android:textSize="@dimen/two_line_secondary_text_size"/> @@ -133,7 +133,7 @@ + android:src="@drawable/ic_unshared"/> - + android:visibility="visible"/> + android:src="@drawable/ic_checkbox_blank_outline"/> + android:src="@drawable/ic_dots_vertical"/>