Browse Source

Merge pull request #2573 from nextcloud/fileDetails

Iteration 2 for file detail & new sharing
Andy Scherzinger 7 years ago
parent
commit
36584a1155
51 changed files with 2316 additions and 852 deletions
  1. 1 0
      drawable_resources/alert-octagon.svg
  2. 1 1
      scripts/lint/lint-results.txt
  3. 6 0
      src/main/AndroidManifest.xml
  4. 30 32
      src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java
  5. 146 107
      src/main/java/com/owncloud/android/files/FileMenuFilter.java
  6. 7 2
      src/main/java/com/owncloud/android/operations/UnshareOperation.java
  7. 21 12
      src/main/java/com/owncloud/android/operations/UpdateSharePermissionsOperation.java
  8. 1 1
      src/main/java/com/owncloud/android/providers/UsersAndGroupsSearchProvider.java
  9. 8 3
      src/main/java/com/owncloud/android/services/OperationsService.java
  10. 23 5
      src/main/java/com/owncloud/android/ui/TextDrawable.java
  11. 5 1
      src/main/java/com/owncloud/android/ui/activity/ContactsPreferenceActivity.java
  12. 6 6
      src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java
  13. 1 0
      src/main/java/com/owncloud/android/ui/activity/FileActivity.java
  14. 214 7
      src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java
  15. 5 6
      src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java
  16. 61 2
      src/main/java/com/owncloud/android/ui/activity/ToolbarActivity.java
  17. 1 1
      src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java
  18. 1 1
      src/main/java/com/owncloud/android/ui/adapter/AccountListAdapter.java
  19. 8 1
      src/main/java/com/owncloud/android/ui/adapter/FileDetailTabAdapter.java
  20. 1 1
      src/main/java/com/owncloud/android/ui/adapter/ShareUserListAdapter.java
  21. 316 62
      src/main/java/com/owncloud/android/ui/adapter/UserListAdapter.java
  22. 78 0
      src/main/java/com/owncloud/android/ui/decoration/SimpleListItemDividerDecoration.java
  23. 53 10
      src/main/java/com/owncloud/android/ui/dialog/ExpirationDatePickerDialogFragment.java
  24. 21 3
      src/main/java/com/owncloud/android/ui/dialog/SendShareDialog.java
  25. 41 29
      src/main/java/com/owncloud/android/ui/dialog/SharePasswordDialogFragment.java
  26. 42 30
      src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.java
  27. 217 255
      src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java
  28. 348 63
      src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java
  29. 11 2
      src/main/java/com/owncloud/android/ui/fragment/FileFragment.java
  30. 7 1
      src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java
  31. 6 29
      src/main/java/com/owncloud/android/ui/fragment/ShareFileFragment.java
  32. 136 0
      src/main/java/com/owncloud/android/ui/fragment/util/FileDetailSharingFragmentHelper.java
  33. 32 4
      src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java
  34. 5 0
      src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java
  35. 5 1
      src/main/java/com/owncloud/android/utils/BitmapUtils.java
  36. 55 32
      src/main/java/com/owncloud/android/utils/DisplayUtils.java
  37. 9 2
      src/main/java/com/owncloud/android/utils/ThemeUtils.java
  38. 31 0
      src/main/res/drawable/divider.xml
  39. 23 0
      src/main/res/drawable/ic_alert_octagon.xml
  40. 0 32
      src/main/res/layout/file_details_empty.xml
  41. 15 26
      src/main/res/layout/file_details_fragment.xml
  42. 41 19
      src/main/res/layout/file_details_share_user_item.xml
  43. 57 46
      src/main/res/layout/file_details_sharing_fragment.xml
  44. 2 2
      src/main/res/layout/share_file_layout.xml
  45. 1 0
      src/main/res/layout/toolbar_standard.xml
  46. 42 0
      src/main/res/menu/file_detail_sharing_link_menu.xml
  47. 60 0
      src/main/res/menu/file_detail_sharing_menu.xml
  48. 87 0
      src/main/res/menu/file_details_actions_menu.xml
  49. 1 3
      src/main/res/values/dims.xml
  50. 16 12
      src/main/res/values/strings.xml
  51. 11 0
      src/main/res/values/styles.xml

+ 1 - 0
drawable_resources/alert-octagon.svg

@@ -0,0 +1 @@
+<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M13,13H11V7H13M12,17.3A1.3,1.3 0 0,1 10.7,16A1.3,1.3 0 0,1 12,14.7A1.3,1.3 0 0,1 13.3,16A1.3,1.3 0 0,1 12,17.3M15.73,3H8.27L3,8.27V15.73L8.27,21H15.73L21,15.73V8.27L15.73,3Z" /></svg>

+ 1 - 1
scripts/lint/lint-results.txt

@@ -1,2 +1,2 @@
 DO NOT TOUCH; GENERATED BY DRONE
-      <span class="mdl-layout-title">Lint Report: 104 warnings</span>
+      <span class="mdl-layout-title">Lint Report: 103 warnings</span>

+ 6 - 0
src/main/AndroidManifest.xml

@@ -80,6 +80,7 @@
         tools:ignore="UnusedAttribute">
         <activity
             android:name=".ui.activity.FileDisplayActivity"
+            android:launchMode="singleTop"
             android:label="@string/app_name"
             android:configChanges="orientation|screenSize"
             android:theme="@style/Theme.ownCloud.Launcher">
@@ -88,6 +89,11 @@
 
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.SEARCH" />
+            </intent-filter>
+            <meta-data android:name="android.app.searchable"
+                android:resource="@xml/users_and_groups_searchable"/>
         </activity>
         <activity android:name=".ui.activity.ManageAccountsActivity" />
         <activity android:name=".ui.activity.UserInfoActivity" />

+ 30 - 32
src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java

@@ -22,7 +22,6 @@
 package com.owncloud.android.datamodel;
 
 import android.accounts.Account;
-import android.accounts.AccountManager;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -773,12 +772,14 @@ public class ThumbnailsCacheManager {
         private final Resources mResources;
         private final float mAvatarRadius;
         private Account mAccount;
-        private String mUsername;
+        private String mUserId;
+        private String mServerName;
+        private Context mContext;
 
 
         public AvatarGenerationTask(AvatarGenerationListener avatarGenerationListener, Object callContext,
                                     FileDataStorageManager storageManager, Account account, Resources resources,
-                                    float avatarRadius) {
+                                    float avatarRadius, String userId, String serverName, Context context) {
             mAvatarGenerationListener = new WeakReference<>(avatarGenerationListener);
             mCallContext = callContext;
             if (storageManager == null) {
@@ -787,6 +788,9 @@ public class ThumbnailsCacheManager {
             mAccount = account;
             mResources = resources;
             mAvatarRadius = avatarRadius;
+            mUserId = userId;
+            mServerName = serverName;
+            mContext = context;
         }
 
         @SuppressFBWarnings("Dm")
@@ -796,20 +800,17 @@ public class ThumbnailsCacheManager {
 
             try {
                 if (mAccount != null) {
-                    OwnCloudAccount ocAccount = new OwnCloudAccount(mAccount,
-                            MainApp.getAppContext());
-                    mClient = OwnCloudClientManagerFactory.getDefaultSingleton().
-                            getClientFor(ocAccount, MainApp.getAppContext());
+                    OwnCloudAccount ocAccount = new OwnCloudAccount(mAccount, mContext);
+                    mClient = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(ocAccount, mContext);
                 }
 
-                mUsername = params[0];
                 thumbnail = doAvatarInBackground();
 
             } catch(OutOfMemoryError oome) {
                 Log_OC.e(TAG, "Out of memory");
             } catch(Throwable t){
                 // the app should never break due to a problem with avatars
-                Log_OC.e(TAG, "Generation of avatar for " + mUsername + " failed", t);
+                Log_OC.e(TAG, "Generation of avatar for " + mUserId + " failed", t);
             }
 
             return thumbnail;
@@ -820,7 +821,7 @@ public class ThumbnailsCacheManager {
                 AvatarGenerationListener listener = mAvatarGenerationListener.get();
                 AvatarGenerationTask avatarWorkerTask = getAvatarWorkerTask(mCallContext);
 
-                if (this == avatarWorkerTask && listener.shouldCallGeneratedCallback(mUsername, mCallContext)) {
+                if (this == avatarWorkerTask && listener.shouldCallGeneratedCallback(mUserId, mCallContext)) {
                     listener.avatarGenerated(drawable, mCallContext);
                 }
             }
@@ -830,7 +831,7 @@ public class ThumbnailsCacheManager {
          * Converts size of file icon from dp to pixel
          * @return int
          */
-        private int getAvatarDimension(){
+        private int getAvatarDimension() {
             // Converts dp to pixel
             Resources r = MainApp.getAppContext().getResources();
             return Math.round(r.getDimension(R.dimen.file_avatar_size));
@@ -839,13 +840,14 @@ public class ThumbnailsCacheManager {
         private @Nullable
         Drawable doAvatarInBackground() {
             Bitmap avatar = null;
-            String username = mUsername;
 
-            ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(
-                    MainApp.getAppContext().getContentResolver());
+            String accountName = mUserId + "@" + mServerName;
+
+            ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(mContext.getContentResolver());
+
+            String eTag = arbitraryDataProvider.getValue(accountName, ThumbnailsCacheManager.AVATAR);
+            String avatarKey = "a_" + mUserId + "_" + mServerName + "_" + eTag;
 
-            String eTag = arbitraryDataProvider.getValue(mAccount, AVATAR);
-            final String imageKey = "a_" + username + "_" + eTag;
             int px = getAvatarDimension();
 
             // Download avatar from server
@@ -854,18 +856,13 @@ public class ThumbnailsCacheManager {
                 if (serverOCVersion.supportsRemoteThumbnails()) {
                     GetMethod get = null;
                     try {
-                        String userId = AccountManager.get(MainApp.getAppContext()).getUserData(mAccount,
-                                com.owncloud.android.lib.common.accounts.AccountUtils.Constants.KEY_USER_ID);
-
-                        if (TextUtils.isEmpty(userId)) {
-                            userId = AccountUtils.getAccountUsername(username);
-                        }
-
-                        String uri = mClient.getBaseUri() + "/index.php/avatar/" + Uri.encode(userId) + "/" + px;
+                        String uri = mClient.getBaseUri() + "/index.php/avatar/" + Uri.encode(mUserId) + "/" + px;
                         Log_OC.d("Avatar", "URI: " + uri);
                         get = new GetMethod(uri);
 
-                        if (!eTag.isEmpty()) {
+                        // only use eTag if available and corresponding avatar is still there 
+                        // (might be deleted from cache)
+                        if (!eTag.isEmpty() && getBitmapFromDiskCache(avatarKey) != null) {
                             get.setRequestHeader("If-None-Match", eTag);
                         }
 
@@ -877,18 +874,19 @@ public class ThumbnailsCacheManager {
                                 // new avatar
                                 InputStream inputStream = get.getResponseBodyAsStream();
 
+                                String newETag = null;
                                 if (get.getResponseHeader(ETAG) != null) {
-                                    eTag = get.getResponseHeader(ETAG).getValue().replace("\"", "");
-                                    arbitraryDataProvider.storeOrUpdateKeyValue(mAccount.name, AVATAR, eTag);
+                                    newETag = get.getResponseHeader(ETAG).getValue().replace("\"", "");
+                                    arbitraryDataProvider.storeOrUpdateKeyValue(accountName, AVATAR, newETag);
                                 }
 
                                 Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
                                 avatar = ThumbnailUtils.extractThumbnail(bitmap, px, px);
 
                                 // Add avatar to cache
-                                if (avatar != null) {
+                                if (avatar != null && !TextUtils.isEmpty(newETag)) {
                                     avatar = handlePNG(avatar, px, px);
-                                    String newImageKey = "a_" + username + "_" + eTag;
+                                    String newImageKey = "a_" + mUserId + "_" + mServerName + "_" + newETag;
                                     addBitmapToCache(newImageKey, avatar);
                                 } else {
                                     return TextDrawable.createAvatar(mAccount.name, mAvatarRadius);
@@ -897,7 +895,7 @@ public class ThumbnailsCacheManager {
 
                             case HttpStatus.SC_NOT_MODIFIED:
                                 // old avatar
-                                avatar = getBitmapFromDiskCache(imageKey);
+                                avatar = getBitmapFromDiskCache(avatarKey);
                                 mClient.exhaustResponse(get.getResponseBodyAsStream());
                                 break;
 
@@ -965,7 +963,7 @@ public class ThumbnailsCacheManager {
         final AvatarGenerationTask avatarWorkerTask = getAvatarWorkerTask(imageView);
 
         if (avatarWorkerTask != null) {
-            final Object usernameData = avatarWorkerTask.mUsername;
+            final Object usernameData = avatarWorkerTask.mUserId;
             // If usernameData is not yet set or it differs from the new data
             if (usernameData == null || !usernameData.equals(file)) {
                 // Cancel previous task
@@ -984,7 +982,7 @@ public class ThumbnailsCacheManager {
         final AvatarGenerationTask avatarWorkerTask = getAvatarWorkerTask(menuItem);
 
         if (avatarWorkerTask != null) {
-            final Object usernameData = avatarWorkerTask.mUsername;
+            final Object usernameData = avatarWorkerTask.mUserId;
             // If usernameData is not yet set or it differs from the new data
             if (usernameData == null || !usernameData.equals(file)) {
                 // Cancel previous task

+ 146 - 107
src/main/java/com/owncloud/android/files/FileMenuFilter.java

@@ -2,7 +2,9 @@
  * ownCloud Android client application
  *
  * @author David A. Velasco
+ * @author Andy Scherzinger
  * Copyright (C) 2015 ownCloud Inc.
+ * Copyright (C) 2018 Andy Scherzinger
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2,
@@ -98,7 +100,6 @@ public class FileMenuFilter {
     public void filter(Menu menu, boolean inSingleFileFragment) {
         if (mFiles == null || mFiles.size() <= 0) {
             hideAll(menu);
-
         } else {
             List<Integer> toShow = new ArrayList<>();
             List<Integer> toHide = new ArrayList<>();
@@ -156,166 +157,187 @@ public class FileMenuFilter {
      */
     private void filter(List<Integer> toShow, List<Integer> toHide, boolean inSingleFileFragment) {
         boolean synchronizing = anyFileSynchronizing();
+        OCCapability capability = mComponentsGetter.getStorageManager().getCapability(mAccount.name);
+        boolean endToEndEncryptionEnabled = capability != null && capability.getEndToEndEncryption().isTrue();
 
-        /// decision is taken for each possible action on a file in the menu
-
-        // DOWNLOAD 
-        if (mFiles.isEmpty() || containsFolder() || anyFileDown() || synchronizing) {
-            toHide.add(R.id.action_download_file);
+        filterDownload(toShow, toHide, synchronizing);
+        filterRename(toShow, toHide, synchronizing);
+        filterMoveCopy(toShow, toHide, synchronizing);
+        filterRemove(toShow, toHide, synchronizing);
+        filterSelectAll(toShow, toHide, inSingleFileFragment);
+        filterDeselectAll(toShow, toHide, inSingleFileFragment);
+        filterOpenWith(toShow, toHide, synchronizing);
+        filterCancelSync(toShow, toHide, synchronizing);
+        filterSync(toShow, toHide, synchronizing);
+        filterShareFile(toShow, toHide, capability);
+        filterDetails(toShow, toHide);
+        filterKeepAvailableOffline(toShow, toHide, synchronizing);
+        filterDontKeepAvailableOffline(toShow, toHide, synchronizing);
+        filterFavorite(toShow, toHide, synchronizing);
+        filterUnfavorite(toShow, toHide, synchronizing);
+        filterEncrypt(toShow, toHide, endToEndEncryptionEnabled);
+        filterUnsetEncrypted(toShow, toHide, endToEndEncryptionEnabled);
+        filterSetPictureAs(toShow, toHide);
+    }
 
+    private void filterShareFile(List<Integer> toShow, List<Integer> toHide, OCCapability capability) {
+        if (containsEncryptedFile() || (!isShareViaLinkAllowed() && !isShareWithUsersAllowed()) ||
+                !isSingleSelection() || !isShareApiEnabled(capability) || mOverflowMenu) {
+            toHide.add(R.id.action_send_share_file);
         } else {
-            toShow.add(R.id.action_download_file);
+            toShow.add(R.id.action_send_share_file);
         }
+    }
 
-        // RENAME
-        if (!isSingleSelection() || synchronizing || containsEncryptedFile() || containsEncryptedFolder()) {
-            toHide.add(R.id.action_rename_file);
-
+    private void filterDetails(List<Integer> toShow, List<Integer> toHide) {
+        if (isSingleSelection()) {
+            toShow.add(R.id.action_see_details);
         } else {
-            toShow.add(R.id.action_rename_file);
+            toHide.add(R.id.action_see_details);
         }
+    }
 
-        // MOVE & COPY
-        if (mFiles.isEmpty() || synchronizing || containsEncryptedFile() || containsEncryptedFolder()) {
-            toHide.add(R.id.action_move);
-            toHide.add(R.id.action_copy);
+    private void filterKeepAvailableOffline(List<Integer> toShow, List<Integer> toHide, boolean synchronizing) {
+        if (!allFiles() || synchronizing || allKeptAvailableOffline()) {
+            toHide.add(R.id.action_keep_files_offline);
         } else {
-            toShow.add(R.id.action_move);
-            toShow.add(R.id.action_copy);
+            toShow.add(R.id.action_keep_files_offline);
         }
+    }
 
-        // REMOVE
-        if (mFiles.isEmpty() || synchronizing || containsEncryptedFolder()) {
-            toHide.add(R.id.action_remove_file);
+    private void filterDontKeepAvailableOffline(List<Integer> toShow, List<Integer> toHide, boolean synchronizing) {
+        if (!allFiles() || synchronizing || allNotKeptAvailableOffline()) {
+            toHide.add(R.id.action_unset_keep_files_offline);
         } else {
-            toShow.add(R.id.action_remove_file);
+            toShow.add(R.id.action_unset_keep_files_offline);
         }
+    }
 
-        // SELECT ALL
-        if (!inSingleFileFragment) {
-            // Show only if at least one item isn't selected.
-            if (mFiles.size() >= mNumberOfAllFiles || mOverflowMenu) {
-                toHide.add(R.id.action_select_all_action_menu);
-            } else {
-                toShow.add(R.id.action_select_all_action_menu);
-            }
+    private void filterFavorite(List<Integer> toShow, List<Integer> toHide, boolean synchronizing) {
+        if (mFiles.isEmpty() || synchronizing || allFavorites()) {
+            toHide.add(R.id.action_favorite);
         } else {
-            // Always hide in single file fragments
-            toHide.add(R.id.action_select_all_action_menu);
+            toShow.add(R.id.action_favorite);
         }
+    }
 
-        // DESELECT ALL
-        if (!inSingleFileFragment) {
-            // Show only if at least one item is selected.
-            if (mFiles.isEmpty() || mOverflowMenu) {
-                toHide.add(R.id.action_deselect_all_action_menu);
-            } else {
-                toShow.add(R.id.action_deselect_all_action_menu);
-            }
-        }else {
-            // Always hide in single file fragments
-            toHide.add(R.id.action_deselect_all_action_menu);
+    private void filterUnfavorite(List<Integer> toShow, List<Integer> toHide, boolean synchronizing) {
+        if (mFiles.isEmpty() || synchronizing || allNotFavorites()) {
+            toHide.add(R.id.action_unset_favorite);
+        } else {
+            toShow.add(R.id.action_unset_favorite);
         }
+    }
 
-        // OPEN WITH (different to preview!)
-        if (!isSingleFile() || !anyFileDown() || synchronizing) {
-            toHide.add(R.id.action_open_file_with);
+    private void filterEncrypt(List<Integer> toShow, List<Integer> toHide, boolean endToEndEncryptionEnabled) {
+        if (mFiles.isEmpty() || !isSingleSelection() || isSingleFile() || isEncryptedFolder()
+                || !endToEndEncryptionEnabled) {
+            toHide.add(R.id.action_encrypted);
         } else {
-            toShow.add(R.id.action_open_file_with);
+            toShow.add(R.id.action_encrypted);
         }
+    }
 
-        // CANCEL SYNCHRONIZATION
-        if (mFiles.isEmpty() || !synchronizing) {
-            toHide.add(R.id.action_cancel_sync);
+    private void filterUnsetEncrypted(List<Integer> toShow, List<Integer> toHide, boolean endToEndEncryptionEnabled) {
+        if (mFiles.isEmpty() || !isSingleSelection() || isSingleFile() || !isEncryptedFolder()
+                || !endToEndEncryptionEnabled) {
+            toHide.add(R.id.action_unset_encrypted);
+        } else {
+            toShow.add(R.id.action_unset_encrypted);
+        }
+    }
 
+    private void filterSetPictureAs(List<Integer> toShow, List<Integer> toHide) {
+        if (isSingleImage() && !MimeTypeUtil.isSVG(mFiles.iterator().next())) {
+            toShow.add(R.id.action_set_as_wallpaper);
         } else {
-            toShow.add(R.id.action_cancel_sync);
+            toHide.add(R.id.action_set_as_wallpaper);
         }
+    }
 
-        // SYNC CONTENTS (BOTH FILE AND FOLDER)
+    private void filterSync(List<Integer> toShow, List<Integer> toHide, boolean synchronizing) {
         if (mFiles.isEmpty() || (!anyFileDown() && !containsFolder()) || synchronizing) {
             toHide.add(R.id.action_sync_file);
-
         } else {
             toShow.add(R.id.action_sync_file);
         }
+    }
 
-        // SHARE FILE
-        boolean shareViaLinkAllowed = (mContext != null &&
-                mContext.getResources().getBoolean(R.bool.share_via_link_feature));
-        boolean shareWithUsersAllowed = (mContext != null &&
-                mContext.getResources().getBoolean(R.bool.share_with_users_feature));
-
-        OCCapability capability = mComponentsGetter.getStorageManager().getCapability(mAccount.name);
-        boolean shareApiEnabled = capability != null &&
-                (capability.getFilesSharingApiEnabled().isTrue() ||
-                        capability.getFilesSharingApiEnabled().isUnknown()
-                );
-        if (containsEncryptedFile() || (!shareViaLinkAllowed && !shareWithUsersAllowed) ||
-                !isSingleSelection() ||
-                !shareApiEnabled || mOverflowMenu) {
-            toHide.add(R.id.action_send_share_file);
-        } else {
-            toShow.add(R.id.action_send_share_file);
-        }
-
-        // SEE DETAILS
-        if (!isSingleFile()) {
-            toHide.add(R.id.action_see_details);
+    private void filterCancelSync(List<Integer> toShow, List<Integer> toHide, boolean synchronizing) {
+        if (mFiles.isEmpty() || !synchronizing) {
+            toHide.add(R.id.action_cancel_sync);
         } else {
-            toShow.add(R.id.action_see_details);
+            toShow.add(R.id.action_cancel_sync);
         }
+    }
 
-        // Kept available offline
-        if (!allFiles() || synchronizing || allKeptAvailableOffline()) {
-            toHide.add(R.id.action_keep_files_offline);
+    private void filterOpenWith(List<Integer> toShow, List<Integer> toHide, boolean synchronizing) {
+        if (!isSingleFile() || !anyFileDown() || synchronizing) {
+            toHide.add(R.id.action_open_file_with);
         } else {
-            toShow.add(R.id.action_keep_files_offline);
+            toShow.add(R.id.action_open_file_with);
         }
+    }
 
-        // Not kept available offline
-        if (!allFiles() || synchronizing || allNotKeptAvailableOffline()) {
-            toHide.add(R.id.action_unset_keep_files_offline);
+    private void filterDeselectAll(List<Integer> toShow, List<Integer> toHide, boolean inSingleFileFragment) {
+        if (inSingleFileFragment) {
+            // Always hide in single file fragments
+            toHide.add(R.id.action_deselect_all_action_menu);
         } else {
-            toShow.add(R.id.action_unset_keep_files_offline);
+            // Show only if at least one item is selected.
+            if (mFiles.isEmpty() || mOverflowMenu) {
+                toHide.add(R.id.action_deselect_all_action_menu);
+            } else {
+                toShow.add(R.id.action_deselect_all_action_menu);
+            }
         }
+    }
 
-        // Favorite
-        if (mFiles.isEmpty() || synchronizing || allFavorites()) {
-            toHide.add(R.id.action_favorite);
+    private void filterSelectAll(List<Integer> toShow, List<Integer> toHide, boolean inSingleFileFragment) {
+        if (!inSingleFileFragment) {
+            // Show only if at least one item isn't selected.
+            if (mFiles.size() >= mNumberOfAllFiles || mOverflowMenu) {
+                toHide.add(R.id.action_select_all_action_menu);
+            } else {
+                toShow.add(R.id.action_select_all_action_menu);
+            }
         } else {
-            toShow.add(R.id.action_favorite);
+            // Always hide in single file fragments
+            toHide.add(R.id.action_select_all_action_menu);
         }
+    }
 
-        // Unfavorite
-        if (mFiles.isEmpty() || synchronizing || allNotFavorites()) {
-            toHide.add(R.id.action_unset_favorite);
+    private void filterRemove(List<Integer> toShow, List<Integer> toHide, boolean synchronizing) {
+        if (mFiles.isEmpty() || synchronizing || containsEncryptedFolder()) {
+            toHide.add(R.id.action_remove_file);
         } else {
-            toShow.add(R.id.action_unset_favorite);
+            toShow.add(R.id.action_remove_file);
         }
+    }
 
-        // Encryption
-        boolean endToEndEncryptionEnabled = capability != null && capability.getEndToEndEncryption().isTrue();
-        if (mFiles.isEmpty() || !isSingleSelection() || isSingleFile() || isEncryptedFolder()
-                || !endToEndEncryptionEnabled) {
-            toHide.add(R.id.action_encrypted);
+    private void filterMoveCopy(List<Integer> toShow, List<Integer> toHide, boolean synchronizing) {
+        if (mFiles.isEmpty() || synchronizing || containsEncryptedFile() || containsEncryptedFolder()) {
+            toHide.add(R.id.action_move);
+            toHide.add(R.id.action_copy);
         } else {
-            toShow.add(R.id.action_encrypted);
+            toShow.add(R.id.action_move);
+            toShow.add(R.id.action_copy);
         }
+    }
 
-        // Un-encrypt
-        if (mFiles.isEmpty() || !isSingleSelection() || isSingleFile() || !isEncryptedFolder()
-                || !endToEndEncryptionEnabled) {
-            toHide.add(R.id.action_unset_encrypted);
+    private void filterRename(List<Integer> toShow, List<Integer> toHide, boolean synchronizing) {
+        if (!isSingleSelection() || synchronizing || containsEncryptedFile() || containsEncryptedFolder()) {
+            toHide.add(R.id.action_rename_file);
         } else {
-            toShow.add(R.id.action_unset_encrypted);
+            toShow.add(R.id.action_rename_file);
         }
+    }
 
-        // SET PICTURE AS
-        if (isSingleImage() && !MimeTypeUtil.isSVG(mFiles.iterator().next())) {
-            toShow.add(R.id.action_set_as_wallpaper);
+    private void filterDownload(List<Integer> toShow, List<Integer> toHide, boolean synchronizing) {
+        if (mFiles.isEmpty() || containsFolder() || anyFileDown() || synchronizing) {
+            toHide.add(R.id.action_download_file);
         } else {
-            toHide.add(R.id.action_set_as_wallpaper);
+            toShow.add(R.id.action_download_file);
         }
     }
 
@@ -364,6 +386,23 @@ public class FileMenuFilter {
         return uploading;
     }
 
+    private boolean isShareApiEnabled(OCCapability capability) {
+        return capability != null &&
+                (capability.getFilesSharingApiEnabled().isTrue() ||
+                        capability.getFilesSharingApiEnabled().isUnknown()
+                );
+    }
+
+    private boolean isShareWithUsersAllowed() {
+        return mContext != null &&
+                mContext.getResources().getBoolean(R.bool.share_with_users_feature);
+    }
+
+    private boolean isShareViaLinkAllowed() {
+        return mContext != null &&
+                mContext.getResources().getBoolean(R.bool.share_via_link_feature);
+    }
+
     private boolean isSingleSelection() {
         return mFiles.size() == SINGLE_SELECT_ITEMS;
     }

+ 7 - 2
src/main/java/com/owncloud/android/operations/UnshareOperation.java

@@ -1,8 +1,10 @@
-/**
+/*
  *   ownCloud Android client application
  *
  *   @author masensio
+ *   @author Andy Scherzinger
  *   Copyright (C) 2015 ownCloud Inc.
+ *   Copyright (C) 2018 Andy Scherzinger
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -15,7 +17,6 @@
  *
  *   You should have received a copy of the GNU General Public License
  *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
  */
 
 package com.owncloud.android.operations;
@@ -109,4 +110,8 @@ public class UnshareOperation extends SyncOperation {
         return result.isSuccess();
     }
 
+    public ShareType getShareType() {
+        return mShareType;
+    }
+
 }

+ 21 - 12
src/main/java/com/owncloud/android/operations/UpdateSharePermissionsOperation.java

@@ -1,8 +1,10 @@
-/**
+/*
  *   ownCloud Android client application
  *
  *   @author David A. Velasco
+ *   @author Andy Scherzinger
  *   Copyright (C) 2015 ownCloud Inc.
+ *   Copyright (C) 2018 Andy Scherzinger
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -15,7 +17,6 @@
  *
  *   You should have received a copy of the GNU General Public License
  *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
  */
 
 package com.owncloud.android.operations;
@@ -31,26 +32,26 @@ import com.owncloud.android.operations.common.SyncOperation;
 
 
 /**
- * Updates an existing private share for a given file
+ * Updates an existing private share for a given file.
  */
-
 public class UpdateSharePermissionsOperation extends SyncOperation {
 
     private long mShareId;
     private int mPermissions;
+    private long mExpirationDateInMillis;
     private String mPath;
 
     /**
      * Constructor
      *
-     * @param shareId       Private {@link OCShare} to update. Mandatory argument
+     * @param shareId Private {@link OCShare} to update. Mandatory argument
      */
     public UpdateSharePermissionsOperation(long shareId) {
         mShareId = shareId;
         mPermissions = -1;
+        mExpirationDateInMillis = 0L;
     }
 
-
     /**
      * Set permissions to update in private share.
      *
@@ -61,6 +62,17 @@ public class UpdateSharePermissionsOperation extends SyncOperation {
         mPermissions = permissions;
     }
 
+    /**
+     * Set expiration date to update private share.
+     *
+     * @param expirationDateInMillis    Expiration date to set to the public link.
+     *                                  A negative value clears the current expiration date.
+     *                                  Zero value (start-of-epoch) results in no update done on
+     *                                  the expiration date.
+     */
+    public void setExpirationDate(long expirationDateInMillis) {
+        mExpirationDateInMillis = expirationDateInMillis;
+    }
 
     @Override
     protected RemoteOperationResult run(OwnCloudClient client) {
@@ -69,18 +81,15 @@ public class UpdateSharePermissionsOperation extends SyncOperation {
 
         if (share == null) {
             // TODO try to get remote share before failing?
-            return new RemoteOperationResult(
-                    RemoteOperationResult.ResultCode.SHARE_NOT_FOUND
-            );
+            return new RemoteOperationResult(RemoteOperationResult.ResultCode.SHARE_NOT_FOUND);
         }
 
         mPath = share.getPath();
 
         // Update remote share with password
-        UpdateRemoteShareOperation updateOp = new UpdateRemoteShareOperation(
-            share.getRemoteId()
-        );
+        UpdateRemoteShareOperation updateOp = new UpdateRemoteShareOperation(share.getRemoteId());
         updateOp.setPermissions(mPermissions);
+        updateOp.setExpirationDate(mExpirationDateInMillis);
         RemoteOperationResult result = updateOp.execute(client);
 
         if (result.isSuccess()) {

+ 1 - 1
src/main/java/com/owncloud/android/providers/UsersAndGroupsSearchProvider.java

@@ -161,7 +161,7 @@ public class UsersAndGroupsSearchProvider extends ContentProvider {
                 userQuery, REQUESTED_PAGE, RESULTS_PER_PAGE
         );
         RemoteOperationResult result = searchRequest.execute(account, getContext());
-        List<JSONObject> names = new ArrayList<JSONObject>();
+        List<JSONObject> names = new ArrayList<>();
         if (result.isSuccess()) {
             for (Object o : result.getData()) {
                 // Get JSonObjects from response

+ 8 - 3
src/main/java/com/owncloud/android/services/OperationsService.java

@@ -1,7 +1,11 @@
-/**
+/*
  *   ownCloud Android client application
  *
+ *   @author masensio
+ *   @author David A. Velasco
+ *   @author Andy Scherzinger
  *   Copyright (C) 2015 ownCloud Inc.
+ *   Copyright (C) 2018 Andy Scherzinger
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -14,7 +18,6 @@
  *
  *   You should have received a copy of the GNU General Public License
  *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
  */
 
 package com.owncloud.android.services;
@@ -583,8 +586,10 @@ public class OperationsService extends Service {
 
                     } else if (shareId > 0) {
                         operation = new UpdateSharePermissionsOperation(shareId);
-                        int permissions = operationIntent.getIntExtra(EXTRA_SHARE_PERMISSIONS, 1);
+                        int permissions = operationIntent.getIntExtra(EXTRA_SHARE_PERMISSIONS, -1);
                         ((UpdateSharePermissionsOperation)operation).setPermissions(permissions);
+                        long expirationDateInMillis = operationIntent.getLongExtra(EXTRA_SHARE_EXPIRATION_DATE_IN_MILLIS, 0L);
+                        ((UpdateSharePermissionsOperation)operation).setExpirationDate(expirationDateInMillis);
                     }
 
                 } else if (action.equals(ACTION_CREATE_SHARE_WITH_SHAREE)) {

+ 23 - 5
src/main/java/com/owncloud/android/ui/TextDrawable.java

@@ -1,19 +1,20 @@
-/**
+/*
  * ownCloud Android client application
  *
  * @author Andy Scherzinger
  * @author Tobias Kaminsiky
  * Copyright (C) 2016 ownCloud Inc.
- * <p/>
+ * Copyright (C) 2018 Andy Scherzinger
+ *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2,
  * as published by the Free Software Foundation.
- * <p/>
+ *
  * 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.
- * <p/>
+ *
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
@@ -103,6 +104,23 @@ public class TextDrawable extends Drawable {
         return createNamedAvatar(username, radiusInDp);
     }
 
+    /**
+     * creates an avatar in form of a TextDrawable with the first letter of the account name in a circle with the
+     * given radius.
+     *
+     * @param userId      userId to use
+     * @param radiusInDp  the circle's radius
+     * @return the avatar as a TextDrawable
+     * @throws UnsupportedEncodingException if the charset is not supported when calculating the color values
+     * @throws NoSuchAlgorithmException     if the specified algorithm is not available when calculating the color values
+     */
+    @NonNull
+    @NextcloudServer(max = 12)
+    public static TextDrawable createAvatarByUserId(String userId, float radiusInDp) throws
+            UnsupportedEncodingException, NoSuchAlgorithmException {
+        return createNamedAvatar(userId, radiusInDp);
+    }
+
     /**
      * creates an avatar in form of a TextDrawable with the first letter of a name in a circle with the
      * given radius.
@@ -130,7 +148,7 @@ public class TextDrawable extends Drawable {
      * @param canvas The canvas to draw into
      */
     @Override
-    public void draw(Canvas canvas) {
+    public void draw(@NonNull Canvas canvas) {
         canvas.drawCircle(mRadius, mRadius, mRadius, mBackground);
         canvas.drawText(mText, mRadius, mRadius - ((mTextPaint.descent() + mTextPaint.ascent()) / 2), mTextPaint);
     }

+ 5 - 1
src/main/java/com/owncloud/android/ui/activity/ContactsPreferenceActivity.java

@@ -51,7 +51,6 @@ import java.util.Set;
 /**
  * This activity shows all settings for contact backup/restore
  */
-
 public class ContactsPreferenceActivity extends FileActivity implements FileFragment.ContainerActivity {
     public static final String TAG = ContactsPreferenceActivity.class.getSimpleName();
 
@@ -171,6 +170,11 @@ public class ContactsPreferenceActivity extends FileActivity implements FileFrag
         // not needed
     }
 
+    @Override
+    public void showDetails(OCFile file, int activeTab) {
+        // not needed
+    }
+
     @Override
     public void onBrowsedDownTo(OCFile folder) {
         // not needed

+ 6 - 6
src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java

@@ -648,8 +648,8 @@ public abstract class DrawerActivity extends ToolbarActivity implements DisplayU
                     View accountEndView = findNavigationViewChildById(R.id.drawer_account_end);
                     accountEndView.setTag(mAvatars[1].name);
 
-                    DisplayUtils.setAvatar(mAvatars[1], this,
-                            mOtherAccountAvatarRadiusDimension, getResources(), getStorageManager(), accountEndView);
+                    DisplayUtils.setAvatar(mAvatars[1], this, mOtherAccountAvatarRadiusDimension, getResources(),
+                            getStorageManager(), accountEndView, this);
                     mAccountEndAccountAvatar.setVisibility(View.VISIBLE);
                 } else {
                     mAccountEndAccountAvatar.setVisibility(View.GONE);
@@ -660,8 +660,8 @@ public abstract class DrawerActivity extends ToolbarActivity implements DisplayU
                     View accountMiddleView = findNavigationViewChildById(R.id.drawer_account_middle);
                     accountMiddleView.setTag(mAvatars[2].name);
 
-                    DisplayUtils.setAvatar(mAvatars[2], this,
-                            mOtherAccountAvatarRadiusDimension, getResources(), getStorageManager(), accountMiddleView);
+                    DisplayUtils.setAvatar(mAvatars[2], this, mOtherAccountAvatarRadiusDimension, getResources(),
+                            getStorageManager(), accountMiddleView, this);
                     mAccountMiddleAccountAvatar.setVisibility(View.VISIBLE);
                 } else {
                     mAccountMiddleAccountAvatar.setVisibility(View.GONE);
@@ -695,7 +695,7 @@ public abstract class DrawerActivity extends ToolbarActivity implements DisplayU
                             account.name)
                             .setIcon(TextDrawable.createAvatar(account.name, mMenuAccountAvatarRadiusDimension));
                     DisplayUtils.setAvatar(account, this, mMenuAccountAvatarRadiusDimension, getResources(),
-                            getStorageManager(), accountMenuItem);
+                            getStorageManager(), accountMenuItem, this);
                 }
             } catch (Exception e) {
                 Log_OC.e(TAG, "Error calculating RGB value for account menu item.", e);
@@ -761,7 +761,7 @@ public abstract class DrawerActivity extends ToolbarActivity implements DisplayU
             currentAccountView.setTag(account.name);
 
             DisplayUtils.setAvatar(account, this, mCurrentAccountAvatarRadiusDimension, getResources(),
-                    getStorageManager(), currentAccountView);
+                    getStorageManager(), currentAccountView, this);
 
             // check and show quota info if available
             getAndDisplayUserQuota();

+ 1 - 0
src/main/java/com/owncloud/android/ui/activity/FileActivity.java

@@ -553,6 +553,7 @@ public abstract class FileActivity extends DrawerActivity
     public void restart() {
         Intent i = new Intent(this, FileDisplayActivity.class);
         i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        i.setAction(FileDisplayActivity.RESTART);
         startActivity(i);
 
         fetchExternalLinks(false);

+ 214 - 7
src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java

@@ -3,8 +3,10 @@
  *
  * @author Bartek Przybylski
  * @author David A. Velasco
+ * @author Andy Scherzinger
  * Copyright (C) 2011  Bartek Przybylski
  * Copyright (C) 2016 ownCloud Inc.
+ * Copyright (C) 2018 Andy Scherzinger
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2,
@@ -26,6 +28,7 @@ import android.accounts.Account;
 import android.accounts.AuthenticatorException;
 import android.annotation.TargetApi;
 import android.app.Activity;
+import android.app.SearchManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -38,13 +41,16 @@ import android.content.SharedPreferences;
 import android.content.SyncRequest;
 import android.content.pm.PackageManager;
 import android.content.res.Resources.NotFoundException;
+import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Parcelable;
 import android.support.annotation.NonNull;
+import android.support.annotation.StringRes;
 import android.support.design.widget.BottomNavigationView;
 import android.support.design.widget.Snackbar;
+import android.support.v4.app.DialogFragment;
 import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentManager;
 import android.support.v4.app.FragmentTransaction;
@@ -71,22 +77,33 @@ import com.owncloud.android.files.services.FileDownloader;
 import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
 import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
+import com.owncloud.android.lib.common.OwnCloudAccount;
+import com.owncloud.android.lib.common.accounts.AccountUtils;
 import com.owncloud.android.lib.common.operations.RemoteOperation;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
 import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.lib.resources.shares.OCShare;
+import com.owncloud.android.lib.resources.shares.ShareType;
 import com.owncloud.android.media.MediaService;
 import com.owncloud.android.media.MediaServiceBinder;
 import com.owncloud.android.operations.CopyFileOperation;
 import com.owncloud.android.operations.CreateFolderOperation;
+import com.owncloud.android.operations.CreateShareViaLinkOperation;
+import com.owncloud.android.operations.CreateShareWithShareeOperation;
 import com.owncloud.android.operations.MoveFileOperation;
 import com.owncloud.android.operations.RefreshFolderOperation;
 import com.owncloud.android.operations.RemoveFileOperation;
 import com.owncloud.android.operations.RenameFileOperation;
 import com.owncloud.android.operations.SynchronizeFileOperation;
+import com.owncloud.android.operations.UnshareOperation;
+import com.owncloud.android.operations.UpdateSharePermissionsOperation;
+import com.owncloud.android.operations.UpdateShareViaLinkOperation;
 import com.owncloud.android.operations.UploadFileOperation;
+import com.owncloud.android.providers.UsersAndGroupsSearchProvider;
 import com.owncloud.android.syncadapter.FileSyncAdapter;
 import com.owncloud.android.ui.dialog.SendShareDialog;
+import com.owncloud.android.ui.dialog.ShareLinkToDialog;
 import com.owncloud.android.ui.dialog.SortingOrderDialogFragment;
 import com.owncloud.android.ui.events.SyncEventFinished;
 import com.owncloud.android.ui.events.TokenPushEvent;
@@ -131,6 +148,8 @@ public class FileDisplayActivity extends HookActivity
         implements FileFragment.ContainerActivity,
         OnEnforceableRefreshListener, SortingOrderDialogFragment.OnSortingOrderListener,
         SendShareDialog.SendShareDialogDownloader {
+
+    public static final String RESTART = "RESTART";
  
     private SyncBroadcastReceiver mSyncBroadcastReceiver;
     private UploadFinishReceiver mUploadFinishReceiver;
@@ -141,6 +160,9 @@ public class FileDisplayActivity extends HookActivity
     private View mLeftFragmentContainer;
     private View mRightFragmentContainer;
 
+    private static final String TAG_PUBLIC_LINK = "PUBLIC_LINK";
+    private static final String FTAG_CHOOSER_DIALOG = "CHOOSER_DIALOG";
+
     private static final String KEY_WAITING_TO_PREVIEW = "WAITING_TO_PREVIEW";
     private static final String KEY_SYNC_IN_PROGRESS = "SYNC_IN_PROGRESS";
     private static final String KEY_WAITING_TO_SEND = "WAITING_TO_SEND";
@@ -484,6 +506,65 @@ public class FileDisplayActivity extends HookActivity
         if (intent.getAction() != null && intent.getAction().equalsIgnoreCase(ACTION_DETAILS)) {
             setIntent(intent);
             setFile(intent.getParcelableExtra(EXTRA_FILE));
+        } else if (RESTART.equals(intent.getAction())) {
+            finish();
+            startActivity(intent);
+        } else // Verify the action and get the query
+            if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
+                String query = intent.getStringExtra(SearchManager.QUERY);
+                Log_OC.w(TAG, "Ignored Intent requesting to query for " + query);
+
+            } else if (UsersAndGroupsSearchProvider.ACTION_SHARE_WITH.equals(intent.getAction())) {
+                Uri data = intent.getData();
+                String dataString = intent.getDataString();
+                String shareWith = dataString.substring(dataString.lastIndexOf('/') + 1);
+
+                ArrayList<String> shareeNames = new ArrayList<>();
+                for (OCShare share : getStorageManager().getSharesWithForAFile(getFile().getRemotePath(), getAccount().name)) {
+                    shareeNames.add(share.getShareWith());
+                }
+
+                if (!shareeNames.contains(shareWith)) {
+                    doShareWith(shareWith, data.getAuthority());
+                }
+
+            } else {
+                Log_OC.e(TAG, "Unexpected intent " + intent.toString());
+            }
+    }
+
+    private void doShareWith(String shareeName, String dataAuthority) {
+
+        ShareType shareType = UsersAndGroupsSearchProvider.getShareType(dataAuthority);
+
+        getFileOperationsHelper().shareFileWithSharee(
+                getFile(),
+                shareeName,
+                shareType,
+                getAppropiatePermissions(shareType)
+        );
+    }
+
+    private int getAppropiatePermissions(ShareType shareType) {
+
+        // check if the Share is FEDERATED
+        boolean isFederated = ShareType.FEDERATED.equals(shareType);
+
+        if (getFile().isSharedWithMe()) {
+            return OCShare.READ_PERMISSION_FLAG;    // minimum permissions
+
+        } else if (isFederated) {
+            if (com.owncloud.android.authentication.AccountUtils
+                    .getServerVersion(getAccount()).isNotReshareableFederatedSupported()) {
+                return (getFile().isFolder() ? OCShare.FEDERATED_PERMISSIONS_FOR_FOLDER_AFTER_OC9 :
+                        OCShare.FEDERATED_PERMISSIONS_FOR_FILE_AFTER_OC9);
+            } else {
+                return (getFile().isFolder() ? OCShare.FEDERATED_PERMISSIONS_FOR_FOLDER_UP_TO_OC9 :
+                        OCShare.FEDERATED_PERMISSIONS_FOR_FILE_UP_TO_OC9);
+            }
+        } else {
+            return (getFile().isFolder() ? OCShare.MAXIMUM_PERMISSIONS_FOR_FOLDER :
+                    OCShare.MAXIMUM_PERMISSIONS_FOR_FILE);
         }
     }
 
@@ -1488,7 +1569,18 @@ public class FileDisplayActivity extends HookActivity
      */
     @Override
     public void showDetails(OCFile file) {
-        Fragment detailFragment = FileDetailFragment.newInstance(file, getAccount());
+        showDetails(file, 0);
+    }
+
+    /**
+     * Shows the information of the {@link OCFile} received as a
+     * parameter in the second fragment.
+     *
+     * @param file {@link OCFile} whose details will be shown
+     * @param activeTab the active tab in the details view
+     */
+    public void showDetails(OCFile file, int activeTab) {
+        Fragment detailFragment = FileDetailFragment.newInstance(file, getAccount(), activeTab);
         setSecondFragment(detailFragment);
         updateFragmentsVisibility(true);
         updateActionBarTitleAndHomeButton(file);
@@ -1614,23 +1706,27 @@ public class FileDisplayActivity extends HookActivity
 
         if (operation instanceof RemoveFileOperation) {
             onRemoveFileOperationFinish((RemoveFileOperation) operation, result);
-
         } else if (operation instanceof RenameFileOperation) {
             onRenameFileOperationFinish((RenameFileOperation) operation, result);
-
         } else if (operation instanceof SynchronizeFileOperation) {
             onSynchronizeFileOperationFinish((SynchronizeFileOperation) operation, result);
-
         } else if (operation instanceof CreateFolderOperation) {
             onCreateFolderOperationFinish((CreateFolderOperation) operation, result);
-
         } else if (operation instanceof MoveFileOperation) {
             onMoveFileOperationFinish((MoveFileOperation) operation, result);
-
         } else if (operation instanceof CopyFileOperation) {
             onCopyFileOperationFinish((CopyFileOperation) operation, result);
+        } else if (operation instanceof CreateShareViaLinkOperation) {
+            onCreateShareViaLinkOperationFinish((CreateShareViaLinkOperation) operation, result);
+        } else if (operation instanceof CreateShareWithShareeOperation) {
+            onUpdateShareInformation(result, R.string.sharee_add_failed);
+        } else if (operation instanceof UpdateShareViaLinkOperation) {
+            onUpdateShareInformation(result, R.string.updating_share_failed);
+        } else if (operation instanceof UpdateSharePermissionsOperation) {
+            onUpdateShareInformation(result, R.string.updating_share_failed);
+        } else if (operation instanceof UnshareOperation) {
+            onUpdateShareInformation(result, R.string.unsharing_failed);
         }
-
     }
 
     private void refreshShowDetails() {
@@ -1723,6 +1819,117 @@ public class FileDisplayActivity extends HookActivity
         }
     }
 
+    private void onCreateShareViaLinkOperationFinish(CreateShareViaLinkOperation operation,
+                                                     RemoteOperationResult result) {
+        FileDetailFragment fileDetailFragment = getShareFileFragment();
+        if (result.isSuccess()) {
+            updateFileFromDB();
+
+            // Create dialog to allow the user choose an app to send the link
+            Intent intentToShareLink = new Intent(Intent.ACTION_SEND);
+
+            // if share to user and share via link multiple ocshares are returned,
+            // therefore filtering for public_link
+            String link = "";
+            for (Object object : result.getData()) {
+                OCShare shareLink = (OCShare) object;
+                if (TAG_PUBLIC_LINK.equalsIgnoreCase(shareLink.getShareType().name())) {
+                    link = shareLink.getShareLink();
+                    break;
+                }
+            }
+
+            intentToShareLink.putExtra(Intent.EXTRA_TEXT, link);
+            intentToShareLink.setType("text/plain");
+
+            String username;
+            try {
+                OwnCloudAccount oca = new OwnCloudAccount(getAccount(), this);
+                if (oca.getDisplayName() != null && !oca.getDisplayName().isEmpty()) {
+                    username = oca.getDisplayName();
+                } else {
+                    username = AccountUtils.getUsernameForAccount(getAccount());
+                }
+            } catch (Exception e) {
+                username = AccountUtils.getUsernameForAccount(getAccount());
+            }
+
+            if (username != null) {
+                intentToShareLink.putExtra(
+                        Intent.EXTRA_SUBJECT,
+                        getString(
+                                R.string.subject_user_shared_with_you,
+                                username,
+                                getFile().getFileName()
+                        )
+                );
+            } else {
+                intentToShareLink.putExtra(
+                        Intent.EXTRA_SUBJECT,
+                        getString(
+                                R.string.subject_shared_with_you,
+                                getFile().getFileName()
+                        )
+                );
+            }
+
+            String[] packagesToExclude = new String[]{getPackageName()};
+            DialogFragment chooserDialog = ShareLinkToDialog.newInstance(intentToShareLink, packagesToExclude);
+            chooserDialog.show(getSupportFragmentManager(), FTAG_CHOOSER_DIALOG);
+
+            fileDetailFragment.getFileDetailSharingFragment().refreshPublicShareFromDB();
+            refreshListOfFilesFragment(false);
+        } else {
+            // Detect Failure (403) --> maybe needs password
+            String password = operation.getPassword();
+            if (result.getCode() == RemoteOperationResult.ResultCode.SHARE_FORBIDDEN    &&
+                    (password == null || password.length() == 0)                        &&
+                    getCapabilities().getFilesSharingPublicEnabled().isUnknown()) {
+                // Was tried without password, but not sure that it's optional.
+
+                // Try with password before giving up; see also ShareFileFragment#OnShareViaLinkListener
+                if (fileDetailFragment != null
+                        && fileDetailFragment.isAdded()) {   // only if added to the view hierarchy!!
+
+                    fileDetailFragment.getFileDetailSharingFragment().requestPasswordForShareViaLink(true);
+                }
+
+            } else {
+                fileDetailFragment.getFileDetailSharingFragment().refreshPublicShareFromDB();
+                Snackbar.make(
+                        findViewById(android.R.id.content),
+                        ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources()),
+                        Snackbar.LENGTH_LONG
+                ).show();
+            }
+        }
+    }
+
+    private void onUpdateShareInformation(RemoteOperationResult result, @StringRes int errorString) {
+        Fragment fileDetailFragment = getSecondFragment();
+
+        if (result.isSuccess()) {
+            updateFileFromDB();
+            refreshListOfFilesFragment(false);
+        } else if (fileDetailFragment.getView() != null) {
+            Snackbar.make(fileDetailFragment.getView(), errorString, Snackbar.LENGTH_LONG).show();
+        }
+
+        if (fileDetailFragment != null && fileDetailFragment instanceof FileDetailFragment) {
+            ((FileDetailFragment) fileDetailFragment).getFileDetailSharingFragment()
+                    .onUpdateShareInformation(result, getFile());
+        }
+    }
+
+    /**
+     * Shortcut to get access to the {@link FileDetailFragment} instance, if any
+     *
+     * @return A {@link FileDetailFragment} instance, or null
+     */
+    private FileDetailFragment getShareFileFragment() {
+        return (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(TAG_SECOND_FRAGMENT);
+    }
+
     /**
      * Updates the view associated to the activity after the finish of an operation trying to copy a
      * file.

+ 5 - 6
src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java

@@ -532,17 +532,16 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
         }
     }
 
-    /**
-     * Shows the information of the {@link OCFile} received as a
-     * parameter in the second fragment.
-     *
-     * @param file          {@link OCFile} whose details will be shown
-     */
     @Override
     public void showDetails(OCFile file) {
         // not used at the moment
     }
 
+    @Override
+    public void showDetails(OCFile file, int activeTab) {
+        // not used at the moment
+    }
+
     /**
      * {@inheritDoc}
      */

+ 61 - 2
src/main/java/com/owncloud/android/ui/activity/ToolbarActivity.java

@@ -22,12 +22,15 @@
 
 package com.owncloud.android.ui.activity;
 
+import android.graphics.Bitmap;
 import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.support.annotation.ColorInt;
 import android.support.v4.content.ContextCompat;
 import android.support.v7.app.ActionBar;
 import android.support.v7.widget.Toolbar;
+import android.widget.ImageView;
 import android.widget.ProgressBar;
 
 import com.owncloud.android.R;
@@ -40,6 +43,7 @@ import com.owncloud.android.utils.ThemeUtils;
  */
 public abstract class ToolbarActivity extends BaseActivity {
     private ProgressBar mProgressBar;
+    private ImageView mPreviewImage;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -66,6 +70,8 @@ public abstract class ToolbarActivity extends BaseActivity {
             ThemeUtils.colorToolbarProgressBar(this, ThemeUtils.primaryColor(this));
         }
 
+        mPreviewImage = findViewById(R.id.preview_image);
+
         ThemeUtils.colorStatusBar(this, primaryDarkColor);
 
         if (toolbar.getOverflowIcon() != null) {
@@ -81,7 +87,7 @@ public abstract class ToolbarActivity extends BaseActivity {
         }
     }
 
-    protected void setupToolbar() {
+    public void setupToolbar() {
         setupToolbar(false);
     }
 
@@ -146,7 +152,60 @@ public abstract class ToolbarActivity extends BaseActivity {
      * @param indeterminate <code>true</code> to enable the indeterminate mode
      */
     public void setIndeterminate(boolean indeterminate) {
-        mProgressBar.setIndeterminate(indeterminate);
+        if (mProgressBar != null) {
+            mProgressBar.setIndeterminate(indeterminate);
+        }
+    }
+
+    /**
+     * Change the visibility for the toolbar's progress bar.
+     *
+     * @param visibility visibility of the progress bar
+     */
+    public void setProgressBarVisibility(int visibility) {
+        if (mProgressBar != null) {
+            mProgressBar.setVisibility(visibility);
+        }
+    }
+
+    /**
+     * Change the visibility for the toolbar's preview image.
+     *
+     * @param visibility visibility of the preview image
+     */
+    public void setPreviewImageVisibility(int visibility) {
+        if (mPreviewImage != null) {
+            mPreviewImage.setVisibility(visibility);
+        }
+    }
+
+    /**
+     * Change the bitmap for the toolbar's preview image.
+     *
+     * @param bitmap bitmap of the preview image
+     */
+    public void setPreviewImageBitmap(Bitmap bitmap) {
+        if (mPreviewImage != null) {
+            mPreviewImage.setImageBitmap(bitmap);
+        }
+    }
+
+    /**
+     * Change the drawable for the toolbar's preview image.
+     *
+     * @param drawable drawable of the preview image
+     */
+    public void setPreviewImageDrawable(Drawable drawable) {
+        if (mPreviewImage != null) {
+            mPreviewImage.setImageDrawable(drawable);
+        }
+    }
+
+    /**
+     * get the toolbar's preview image view.
+     */
+    public ImageView getPreviewImageView() {
+            return mPreviewImage;
     }
 
     /**

+ 1 - 1
src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java

@@ -263,7 +263,7 @@ public class UserInfoActivity extends FileActivity {
         userName.setText(account.name);
         avatar.setTag(account.name);
         DisplayUtils.setAvatar(account, UserInfoActivity.this, mCurrentAccountAvatarRadiusDimension, getResources(),
-                getStorageManager(), avatar);
+                getStorageManager(), avatar, this);
 
         int tint = ThemeUtils.primaryColor(account, this);
 

+ 1 - 1
src/main/java/com/owncloud/android/ui/adapter/AccountListAdapter.java

@@ -153,7 +153,7 @@ public class AccountListAdapter extends ArrayAdapter<AccountListItem> implements
             View viewItem = viewHolder.imageViewItem;
             viewItem.setTag(account.name);
             DisplayUtils.setAvatar(account, this, mAccountAvatarRadiusDimension, mContext.getResources(),
-                    mContext.getStorageManager(), viewItem);
+                    mContext.getStorageManager(), viewItem, mContext);
         } catch (Exception e) {
             Log_OC.e(TAG, "Error calculating RGB value for account list item.", e);
             // use user icon as a fallback

+ 8 - 1
src/main/java/com/owncloud/android/ui/adapter/FileDetailTabAdapter.java

@@ -36,6 +36,8 @@ public class FileDetailTabAdapter extends FragmentStatePagerAdapter {
     private OCFile file;
     private Account account;
 
+    private FileDetailSharingFragment fileDetailSharingFragment;
+
     public FileDetailTabAdapter(FragmentManager fm, OCFile file, Account account) {
         super(fm);
 
@@ -49,12 +51,17 @@ public class FileDetailTabAdapter extends FragmentStatePagerAdapter {
             case 0:
                 return FileDetailActivitiesFragment.newInstance(file, account);
             case 1:
-                return FileDetailSharingFragment.newInstance(file, account);
+                fileDetailSharingFragment = FileDetailSharingFragment.newInstance(file, account);
+                return fileDetailSharingFragment;
             default:
                 return null;
         }
     }
 
+    public FileDetailSharingFragment getFileDetailSharingFragment() {
+        return fileDetailSharingFragment;
+    }
+
     @Override
     public int getCount() {
         return 2;

+ 1 - 1
src/main/java/com/owncloud/android/ui/adapter/ShareUserListAdapter.java

@@ -1,4 +1,4 @@
-/**
+/*
  *   ownCloud Android client application
  *
  *   @author masensio

+ 316 - 62
src/main/java/com/owncloud/android/ui/adapter/UserListAdapter.java

@@ -1,113 +1,367 @@
-/**
- *   ownCloud Android client application
- *
- *   @author masensio
- *   Copyright (C) 2015 ownCloud Inc.
+/*
+ * Nextcloud Android client application
  *
- *   This program is free software: you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License version 2,
- *   as published by the Free Software Foundation.
+ * @author masensio
+ * @author Andy Scherzinger
+ * Copyright (C) 2015 ownCloud GmbH
+ * Copyright (C) 2018 Andy Scherzinger
  *
- *   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.
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
  *
- *   You should have received a copy of the GNU General Public License
- *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ * 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 <http://www.gnu.org/licenses/>.
  */
 
 package com.owncloud.android.ui.adapter;
 
+import android.accounts.Account;
 import android.content.Context;
+import android.graphics.drawable.Drawable;
 import android.support.annotation.NonNull;
+import android.support.v4.app.FragmentManager;
+import android.support.v7.widget.AppCompatCheckBox;
+import android.support.v7.widget.RecyclerView;
 import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
 import android.widget.ImageView;
+import android.widget.PopupMenu;
 import android.widget.TextView;
 
 import com.owncloud.android.R;
+import com.owncloud.android.authentication.AccountUtils;
+import com.owncloud.android.datamodel.FileDataStorageManager;
+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.status.OCCapability;
 import com.owncloud.android.ui.TextDrawable;
+import com.owncloud.android.ui.dialog.ExpirationDatePickerDialogFragment;
+import com.owncloud.android.ui.fragment.util.FileDetailSharingFragmentHelper;
+import com.owncloud.android.utils.DisplayUtils;
+import com.owncloud.android.utils.ThemeUtils;
 
 import java.io.UnsupportedEncodingException;
 import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
+import java.util.List;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
 
 /**
- * Adapter to show a user/group in Share With List in Details View
+ * Adapter to show a user/group/email/remote in Sharing list in file details view.
  */
-public class UserListAdapter extends ArrayAdapter {
+public class UserListAdapter extends RecyclerView.Adapter<UserListAdapter.UserViewHolder>
+        implements DisplayUtils.AvatarGenerationListener {
 
-    private Context mContext;
-    private ArrayList<OCShare> mShares;
-    private float mAvatarRadiusDimension;
+    private ShareeListAdapterListener listener;
+    private OCCapability capabilities;
+    private FragmentManager fragmentManager;
+    private Context context;
+    private int accentColor;
+    private List<OCShare> shares;
+    private float avatarRadiusDimension;
+    private Account account;
+    private OCFile file;
+    private FileDataStorageManager storageManager;
 
-    public UserListAdapter(Context context, int resource, ArrayList<OCShare> shares) {
-        super(context, resource);
-        mContext = context;
-        mShares = shares;
-
-        mAvatarRadiusDimension = context.getResources().getDimension(R.dimen.standard_padding);
-    }
+    public UserListAdapter(FragmentManager fragmentManager, Context context, List<OCShare> shares, Account account,
+                           OCFile file, ShareeListAdapterListener listener) {
+        this.context = context;
+        this.fragmentManager = fragmentManager;
+        this.shares = shares;
+        this.listener = listener;
+        this.account = account;
+        this.file = file;
 
-    @Override
-    public int getCount() {
-        return mShares.size();
+        accentColor = ThemeUtils.primaryAccentColor(context);
+        storageManager = new FileDataStorageManager(account, context.getContentResolver());
+        capabilities = storageManager.getCapability(account.name);
+        avatarRadiusDimension = context.getResources().getDimension(R.dimen.user_icon_radius);
     }
 
+    @NonNull
     @Override
-    public Object getItem(int position) {
-        return mShares.get(position);
+    public UserViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.file_details_share_user_item, parent, false);
+        return new UserViewHolder(v);
     }
 
     @Override
-    public long getItemId(int position) {
-        return 0;
-    }
+    public void onBindViewHolder(@NonNull UserViewHolder holder, int position) {
+        if (shares != null && shares.size() > position) {
+            final OCShare share = shares.get(position);
 
-    @Override
-    public @NonNull View getView(final int position, View convertView, @NonNull ViewGroup parent) {
-        View view = convertView;
-        if (view == null) {
-            LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-            view = inflater.inflate(R.layout.file_details_share_user_item, parent, false);
-        }
-
-        if (mShares != null && mShares.size() > position) {
-            OCShare share = mShares.get(position);
-
-            TextView userName = view.findViewById(R.id.userOrGroupName);
-            ImageView icon = view.findViewById(R.id.userIcon);
             String name = share.getSharedWithDisplayName();
             if (share.getShareType() == ShareType.GROUP) {
-                name = getContext().getString(R.string.share_group_clarification, name);
+                name = context.getString(R.string.share_group_clarification, name);
                 try {
-                    icon.setImageDrawable(TextDrawable.createNamedAvatar(name, mAvatarRadiusDimension));
+                    holder.avatar.setImageDrawable(TextDrawable.createNamedAvatar(name, avatarRadiusDimension));
                 } catch (UnsupportedEncodingException | NoSuchAlgorithmException e) {
-                    icon.setImageResource(R.drawable.ic_group);
+                    holder.avatar.setImageResource(R.drawable.ic_group);
                 }
             } else if (share.getShareType() == ShareType.EMAIL) {
-                name = getContext().getString(R.string.share_email_clarification, name);
+                name = context.getString(R.string.share_email_clarification, name);
                 try {
-                    icon.setImageDrawable(TextDrawable.createNamedAvatar(name, mAvatarRadiusDimension));
+                    holder.avatar.setImageDrawable(TextDrawable.createNamedAvatar(name, avatarRadiusDimension));
                 } catch (UnsupportedEncodingException | NoSuchAlgorithmException e) {
-                    icon.setImageResource(R.drawable.ic_email);
+                    holder.avatar.setImageResource(R.drawable.ic_email);
                 }
             } else {
-                try {
-                    icon.setImageDrawable(TextDrawable.createNamedAvatar(name, mAvatarRadiusDimension));
-                } catch (UnsupportedEncodingException | NoSuchAlgorithmException e) {
-                    icon.setImageResource(R.drawable.ic_user);
+                holder.avatar.setTag(share.getShareWith());
+                DisplayUtils.setAvatar(account, share.getShareWith(), this, avatarRadiusDimension,
+                        context.getResources(), storageManager, holder.avatar, context);
+            }
+            holder.name.setText(name);
+
+            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));
+        }
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return shares.get(position).getId();
+    }
+
+    @Override
+    public int getItemCount() {
+        return shares.size();
+    }
+
+    private void allowEditClick(AppCompatCheckBox checkBox, @NonNull OCShare share) {
+        if (!share.isFolder()) {
+            share.setPermissions(listener.updatePermissionsToShare(
+                    share,
+                    canReshare(share),
+                    checkBox.isChecked(),
+                    false,
+                    false,
+                    false
+            ));
+        } else {
+            share.setPermissions(listener.updatePermissionsToShare(
+                    share,
+                    canReshare(share),
+                    checkBox.isChecked(),
+                    checkBox.isChecked(),
+                    checkBox.isChecked(),
+                    checkBox.isChecked()
+            ));
+        }
+    }
+
+    private void onOverflowIconClicked(View view, AppCompatCheckBox allowEditsCheckBox, OCShare share) {
+        // use grey as fallback for elements where custom theming is not available
+        if (ThemeUtils.themingEnabled(context)) {
+            context.getTheme().applyStyle(R.style.FallbackThemingTheme, true);
+        }
+        PopupMenu popup = new PopupMenu(context, view);
+        popup.inflate(R.menu.file_detail_sharing_menu);
+
+        prepareOptionsMenu(popup.getMenu(), share);
+
+        popup.setOnMenuItemClickListener(item -> optionsItemSelected(popup.getMenu(), item, allowEditsCheckBox, share));
+        popup.show();
+    }
+
+    /**
+     * Updates the sharee's menu with the current permissions of the {@link OCShare}
+     *
+     * @param menu  the menu of the sharee/shared file
+     * @param share the shared file
+     */
+    private void prepareOptionsMenu(Menu menu, OCShare share) {
+
+        MenuItem reshareItem = menu.findItem(R.id.action_can_reshare);
+        if (isReshareForbidden(share)) {
+            reshareItem.setVisible(false);
+        }
+        reshareItem.setChecked(canReshare(share));
+
+        MenuItem editCreateItem = menu.findItem(R.id.action_can_edit_create);
+        MenuItem editChangeItem = menu.findItem(R.id.action_can_edit_change);
+        MenuItem editDeleteItem = menu.findItem(R.id.action_can_edit_delete);
+        if (file.isFolder() && isEditOptionsAvailable(share)) {
+            /// TODO change areEditOptionsAvailable in order to delete !isFederated
+            editCreateItem.setChecked(canCreate(share));
+            editChangeItem.setChecked(canUpdate(share));
+            editDeleteItem.setChecked(canDelete(share));
+        } else {
+            editCreateItem.setVisible(false);
+            editChangeItem.setVisible(false);
+            editDeleteItem.setVisible(false);
+        }
+
+        FileDetailSharingFragmentHelper.setupExpirationDateMenuItem(
+                menu.findItem(R.id.action_expiration_date), share.getExpirationDate(), context.getResources());
+    }
+
+    private boolean isEditOptionsAvailable(OCShare share) {
+        return !ShareType.FEDERATED.equals(share.getShareType())
+                || AccountUtils.getServerVersion(account).isNotReshareableFederatedSupported();
+    }
+
+    private boolean isReshareForbidden(OCShare share) {
+        return ShareType.FEDERATED.equals(share.getShareType()) ||
+                (capabilities != null && capabilities.getFilesSharingResharing().isFalse());
+    }
+
+    private boolean canEdit(OCShare share) {
+        return (share.getPermissions() &
+                (OCShare.CREATE_PERMISSION_FLAG | OCShare.UPDATE_PERMISSION_FLAG | OCShare.DELETE_PERMISSION_FLAG)) > 0;
+    }
+
+    private boolean canCreate(OCShare share) {
+        return (share.getPermissions() & OCShare.CREATE_PERMISSION_FLAG) > 0;
+    }
+
+    private boolean canUpdate(OCShare share) {
+        return (share.getPermissions() & OCShare.UPDATE_PERMISSION_FLAG) > 0;
+    }
+
+    private boolean canDelete(OCShare share) {
+        return (share.getPermissions() & OCShare.DELETE_PERMISSION_FLAG) > 0;
+    }
+
+    private boolean canReshare(OCShare share) {
+        return (share.getPermissions() & OCShare.SHARE_PERMISSION_FLAG) > 0;
+    }
+
+    private boolean optionsItemSelected(Menu menu, MenuItem item, AppCompatCheckBox allowEditsCheckBox, OCShare share) {
+        switch (item.getItemId()) {
+            case R.id.action_can_edit_create:
+            case R.id.action_can_edit_change:
+            case R.id.action_can_edit_delete: {
+                item.setChecked(!item.isChecked());
+                if (item.isChecked() && !allowEditsCheckBox.isChecked()) {
+                    allowEditsCheckBox.setChecked(true);
                 }
+                share.setPermissions(
+                        updatePermissionsToShare(
+                                share,
+                                menu.findItem(R.id.action_can_reshare).isChecked(),
+                                allowEditsCheckBox.isChecked(),
+                                menu.findItem(R.id.action_can_edit_create).isChecked(),
+                                menu.findItem(R.id.action_can_edit_change).isChecked(),
+                                menu.findItem(R.id.action_can_edit_delete).isChecked())
+                );
+                return true;
+            }
+            case R.id.action_can_reshare: {
+                item.setChecked(!item.isChecked());
+                share.setPermissions(
+                        updatePermissionsToShare(
+                                share,
+                                menu.findItem(R.id.action_can_reshare).isChecked(),
+                                allowEditsCheckBox.isChecked(),
+                                menu.findItem(R.id.action_can_edit_create).isChecked(),
+                                menu.findItem(R.id.action_can_edit_change).isChecked(),
+                                menu.findItem(R.id.action_can_edit_delete).isChecked())
+                );
+                return true;
             }
-            userName.setText(name);
+            case R.id.action_unshare: {
+                listener.unshareWith(share);
+                shares.remove(share);
+                notifyDataSetChanged();
+                return true;
+            }
+            case R.id.action_expiration_date: {
+                ExpirationDatePickerDialogFragment dialog = ExpirationDatePickerDialogFragment.newInstance(share, -1);
+                dialog.show(
+                        fragmentManager,
+                        ExpirationDatePickerDialogFragment.DATE_PICKER_DIALOG
+                );
+                return true;
+            }
+            default:
+                return true;
+        }
+    }
+
+    private int updatePermissionsToShare(OCShare share, boolean canReshare, boolean canEdit, boolean canEditCreate,
+                                         boolean canEditChange, boolean canEditDelete) {
+        return listener.updatePermissionsToShare(
+                share,
+                canReshare,
+                canEdit,
+                canEditCreate,
+                canEditChange,
+                canEditDelete
+        );
+    }
+
+    @Override
+    public void avatarGenerated(Drawable avatarDrawable, Object callContext) {
+        if (callContext instanceof ImageView) {
+            ImageView iv = (ImageView) callContext;
+            iv.setImageDrawable(avatarDrawable);
+        }
+    }
 
+    @Override
+    public boolean shouldCallGeneratedCallback(String tag, Object callContext) {
+        if (callContext instanceof ImageView) {
+            ImageView iv = (ImageView) callContext;
+            return String.valueOf(iv.getTag()).equals(tag);
         }
-        return view;
+        return false;
+    }
+
+    class UserViewHolder extends RecyclerView.ViewHolder {
+        @BindView(R.id.avatar)
+        ImageView avatar;
+        @BindView(R.id.name)
+        TextView name;
+        @BindView(R.id.allowEditing)
+        AppCompatCheckBox allowEditing;
+        @BindView(R.id.editShareButton)
+        ImageView editShareButton;
+
+        UserViewHolder(View itemView) {
+            super(itemView);
+            ButterKnife.bind(this, itemView);
+        }
+    }
+
+    public interface ShareeListAdapterListener {
+        /**
+         * unshare with given sharee {@link OCShare}.
+         *
+         * @param share the share
+         */
+        void unshareWith(OCShare share);
+
+        /**
+         * Updates the permissions of the {@link OCShare}.
+         *
+         * @param share         the share to be updated
+         * @param canReshare    reshare permission
+         * @param canEdit       edit permission
+         * @param canEditCreate create permission (folders only)
+         * @param canEditChange change permission (folders only)
+         * @param canEditDelete delete permission (folders only)
+         * @return permissions value set
+         */
+        int updatePermissionsToShare(OCShare share,
+                                     boolean canReshare,
+                                     boolean canEdit,
+                                     boolean canEditCreate,
+                                     boolean canEditChange,
+                                     boolean canEditDelete);
     }
-}
+}

+ 78 - 0
src/main/java/com/owncloud/android/ui/decoration/SimpleListItemDividerDecoration.java

@@ -0,0 +1,78 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Andy Scherzinger
+ * Copyright (C) 2018 Andy Scherzinger
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.owncloud.android.ui.decoration;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.support.v7.widget.DividerItemDecoration;
+import android.support.v7.widget.RecyclerView;
+import android.util.DisplayMetrics;
+import android.view.View;
+
+/**
+ * DividerItemDecoration based on {@link DividerItemDecoration} adding a 72dp left padding.
+ */
+public class SimpleListItemDividerDecoration extends DividerItemDecoration {
+    private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
+
+    private final Rect bounds = new Rect();
+    private Drawable divider;
+    private int leftPadding;
+
+    /**
+     * Default divider will be used
+     */
+    public SimpleListItemDividerDecoration(Context context) {
+        super(context, DividerItemDecoration.VERTICAL);
+        final TypedArray styledAttributes = context.obtainStyledAttributes(ATTRS);
+        divider = styledAttributes.getDrawable(0);
+        leftPadding = Math.round(72 * (context.getResources().getDisplayMetrics().xdpi / DisplayMetrics.DENSITY_DEFAULT));
+        styledAttributes.recycle();
+    }
+
+    @Override
+    public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
+        canvas.save();
+        final int right;
+        //noinspection AndroidLintNewApi - NewApi lint fails to handle overrides.
+        if (parent.getClipToPadding()) {
+            right = parent.getWidth() - parent.getPaddingRight();
+            canvas.clipRect(leftPadding, parent.getPaddingTop(), right,
+                    parent.getHeight() - parent.getPaddingBottom());
+        } else {
+            right = parent.getWidth();
+        }
+
+        final int childCount = parent.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View child = parent.getChildAt(i);
+            parent.getDecoratedBoundsWithMargins(child, bounds);
+            final int bottom = bounds.bottom + Math.round(child.getTranslationY());
+            final int top = bottom - 1;
+            divider.setBounds(leftPadding, top, right, bottom);
+            divider.draw(canvas);
+        }
+        canvas.restore();
+    }
+}

+ 53 - 10
src/main/java/com/owncloud/android/ui/dialog/ExpirationDatePickerDialogFragment.java

@@ -1,8 +1,10 @@
-/**
+/*
  *   ownCloud Android client application
  *
  *   @author David A. Velasco
+ *   @author Andy Scherzinger
  *   Copyright (C) 2015 ownCloud Inc.
+ *   Copyright (C) 2018 Andy Scherzinger
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -15,7 +17,6 @@
  *
  *   You should have received a copy of the GNU General Public License
  *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
  */
 
 package com.owncloud.android.ui.dialog;
@@ -24,11 +25,14 @@ package com.owncloud.android.ui.dialog;
 import android.app.DatePickerDialog;
 import android.app.Dialog;
 import android.os.Bundle;
+import android.support.annotation.NonNull;
 import android.support.v4.app.DialogFragment;
 import android.text.format.DateUtils;
 import android.widget.DatePicker;
 
+import com.owncloud.android.R;
 import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.lib.resources.shares.OCShare;
 import com.owncloud.android.ui.activity.FileActivity;
 
 import java.util.Calendar;
@@ -46,18 +50,24 @@ public class ExpirationDatePickerDialogFragment
     /** Parameter constant for {@link OCFile} instance to set the expiration date */
     private static final String ARG_FILE = "FILE";
 
+    /** Parameter constant for {@link OCShare} instance to set the expiration date */
+    private static final String ARG_SHARE = "SHARE";
+
     /** Parameter constant for date chosen initially */
     private static final String ARG_CHOSEN_DATE_IN_MILLIS = "CHOSEN_DATE_IN_MILLIS";
 
     /** File to bind an expiration date */
-    private OCFile mFile;
+    private OCFile file;
+
+    /** Share to bind an expiration date */
+    private OCShare share;
 
     /**
      *  Factory method to create new instances
      *
      *  @param file                 File to bind an expiration date
      *  @param chosenDateInMillis   Date chosen when the dialog appears
-     *  @return                     New dialog instance
+     *  @return New dialog instance
      */
     public static ExpirationDatePickerDialogFragment newInstance(OCFile file, long chosenDateInMillis) {
         Bundle arguments = new Bundle();
@@ -69,15 +79,34 @@ public class ExpirationDatePickerDialogFragment
         return dialog;
     }
 
+    /**
+     *  Factory method to create new instances
+     *
+     *  @param share                 share to bind an expiration date
+     *  @param chosenDateInMillis   Date chosen when the dialog appears
+     *  @return New dialog instance
+     */
+    public static ExpirationDatePickerDialogFragment newInstance(OCShare share, long chosenDateInMillis) {
+        Bundle arguments = new Bundle();
+        arguments.putParcelable(ARG_SHARE, share);
+        arguments.putLong(ARG_CHOSEN_DATE_IN_MILLIS, chosenDateInMillis);
+
+        ExpirationDatePickerDialogFragment dialog = new ExpirationDatePickerDialogFragment();
+        dialog.setArguments(arguments);
+        return dialog;
+    }
+
     /**
      * {@inheritDoc}
      *
-     * @return      A new dialog to let the user choose an expiration date that will be bound to a share link.
+     * @return A new dialog to let the user choose an expiration date that will be bound to a share link.
      */
     @Override
+    @NonNull
     public Dialog onCreateDialog(Bundle savedInstanceState) {
         // Load arguments
-        mFile = getArguments().getParcelable(ARG_FILE);
+        file = getArguments().getParcelable(ARG_FILE);
+        share = getArguments().getParcelable(ARG_SHARE);
 
         // Chosen date received as an argument must be later than tomorrow ; default to tomorrow in other case
         final Calendar chosenDate = Calendar.getInstance();
@@ -92,11 +121,23 @@ public class ExpirationDatePickerDialogFragment
         // Create a new instance of DatePickerDialog
         DatePickerDialog dialog = new DatePickerDialog(
                 getActivity(),
+                R.style.FallbackDatePickerDialogTheme,
                 this,
                 chosenDate.get(Calendar.YEAR),
                 chosenDate.get(Calendar.MONTH),
                 chosenDate.get(Calendar.DAY_OF_MONTH)
         );
+        dialog.setButton(
+                Dialog.BUTTON_NEUTRAL,
+                getText(R.string.share_via_link_unset_password),
+                (dialog1, which) -> {
+                    if (file != null) {
+                        ((FileActivity) getActivity()).getFileOperationsHelper()
+                                .setExpirationDateToShareViaLink(file, -1);
+                    } else if (share != null) {
+                        ((FileActivity) getActivity()).getFileOperationsHelper().setExpirationDateToShare(share,-1);
+                    }
+                });
 
         // Prevent days in the past may be chosen
         DatePicker picker = dialog.getDatePicker();
@@ -127,9 +168,11 @@ public class ExpirationDatePickerDialogFragment
         chosenDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
         long chosenDateInMillis = chosenDate.getTimeInMillis();
 
-        ((FileActivity)getActivity()).getFileOperationsHelper().setExpirationDateToShareViaLink(
-                mFile,
-                chosenDateInMillis
-        );
+        if (file != null) {
+            ((FileActivity) getActivity()).getFileOperationsHelper()
+                    .setExpirationDateToShareViaLink(file, chosenDateInMillis);
+        } else if (share != null) {
+            ((FileActivity) getActivity()).getFileOperationsHelper().setExpirationDateToShare(share,chosenDateInMillis);
+        }
     }
 }

+ 21 - 3
src/main/java/com/owncloud/android/ui/dialog/SendShareDialog.java

@@ -16,11 +16,13 @@ import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ImageView;
+import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import com.owncloud.android.R;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.ui.activity.FileDisplayActivity;
 import com.owncloud.android.ui.adapter.SendButtonAdapter;
 import com.owncloud.android.ui.components.SendButtonData;
 import com.owncloud.android.ui.helpers.FileOperationsHelper;
@@ -34,8 +36,10 @@ import java.util.List;
  * Nextcloud Android client application
  *
  * @author Tobias Kaminsky
+ * @author Andy Scherzinger
  * Copyright (C) 2017 Tobias Kaminsky
  * Copyright (C) 2017 Nextcloud GmbH.
+ * Copyright (C) 2018 Andy Scherzinger
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as published by
@@ -53,20 +57,23 @@ import java.util.List;
 public class SendShareDialog extends BottomSheetDialogFragment {
 
     private static final String KEY_OCFILE = "KEY_OCFILE";
+    private static final String KEY_HIDE_NCSHARING_OPTIONS = "KEY_HIDE_NCSHARING_OPTIONS";
     private static final String TAG = SendShareDialog.class.getSimpleName();
     public static final String PACKAGE_NAME = "PACKAGE_NAME";
     public static final String ACTIVITY_NAME = "ACTIVITY_NAME";
 
     private View view;
     private OCFile file;
+    private boolean hideNcSharingOptions;
     private FileOperationsHelper fileOperationsHelper;
 
-    public static SendShareDialog newInstance(OCFile file) {
+    public static SendShareDialog newInstance(OCFile file, boolean hideNcSharingOptions) {
 
         SendShareDialog dialogFragment = new SendShareDialog();
 
         Bundle args = new Bundle();
         args.putParcelable(KEY_OCFILE, file);
+        args.putBoolean(KEY_HIDE_NCSHARING_OPTIONS, hideNcSharingOptions);
         dialogFragment.setArguments(args);
 
         return dialogFragment;
@@ -81,6 +88,7 @@ public class SendShareDialog extends BottomSheetDialogFragment {
         view = null;
 
         file = getArguments().getParcelable(KEY_OCFILE);
+        hideNcSharingOptions = getArguments().getBoolean(KEY_HIDE_NCSHARING_OPTIONS, false);
     }
 
     @Nullable
@@ -89,6 +97,9 @@ public class SendShareDialog extends BottomSheetDialogFragment {
 
         view = inflater.inflate(R.layout.send_share_fragment, container, false);
 
+        LinearLayout sendShareButtons = view.findViewById(R.id.send_share_buttons);
+        View divider = view.findViewById(R.id.divider);
+
         // Share with people
         TextView sharePeopleText = view.findViewById(R.id.share_people_button);
         sharePeopleText.setOnClickListener(v -> shareFile(file));
@@ -105,7 +116,10 @@ public class SendShareDialog extends BottomSheetDialogFragment {
         themeShareButtonImage(shareLinkImageView);
         shareLinkImageView.setOnClickListener(v -> shareFile(file));
 
-        if (file.isSharedWithMe() && !file.canReshare()) {
+        if (hideNcSharingOptions) {
+            sendShareButtons.setVisibility(View.GONE);
+            divider.setVisibility(View.GONE);
+        } else if (file.isSharedWithMe() && !file.canReshare()) {
             showResharingNotAllowedSnackbar();
 
             if (file.isFolder()) {
@@ -216,7 +230,11 @@ public class SendShareDialog extends BottomSheetDialogFragment {
     }
 
     private void shareFile(OCFile file) {
-        fileOperationsHelper.showShareFile(file);
+        if (getActivity() instanceof FileDisplayActivity) {
+            ((FileDisplayActivity) getActivity()).showDetails(file, 1);
+        } else {
+            fileOperationsHelper.showShareFile(file);
+        }
         dismiss();
     }
 

+ 41 - 29
src/main/java/com/owncloud/android/ui/dialog/SharePasswordDialogFragment.java

@@ -1,7 +1,10 @@
-/**
+/*
  *   ownCloud Android client application
+ *
  *   @author masensio
- *   Copyright (C) 2015 ownCloud Inc.
+ *   @author Andy Scherzinger
+ *   Copyright (C) 2015 ownCloud GmbH.
+ *   Copyright (C) 2018 Andy Scherzinger
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -14,7 +17,6 @@
  *
  *   You should have received a copy of the GNU General Public License
  *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
  */
 package com.owncloud.android.ui.dialog;
 
@@ -23,11 +25,13 @@ import android.content.DialogInterface;
 import android.graphics.PorterDuff;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.design.widget.Snackbar;
 import android.support.v4.app.DialogFragment;
 import android.support.v7.app.AlertDialog;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.WindowManager;
 import android.widget.EditText;
 import android.widget.TextView;
@@ -42,16 +46,14 @@ import com.owncloud.android.utils.ThemeUtils;
  *
  * Triggers the share when the password is introduced.
  */
-
-public class SharePasswordDialogFragment extends DialogFragment
-        implements DialogInterface.OnClickListener {
+public class SharePasswordDialogFragment extends DialogFragment implements DialogInterface.OnClickListener {
 
     private static final String ARG_FILE = "FILE";
     private static final String ARG_CREATE_SHARE = "CREATE_SHARE";
     public static final String PASSWORD_FRAGMENT = "PASSWORD_FRAGMENT";
 
-    private OCFile mFile;
-    private boolean mCreateShare;
+    private OCFile file;
+    private boolean createShare;
 
     @Override
     public void onStart() {
@@ -60,16 +62,18 @@ public class SharePasswordDialogFragment extends DialogFragment
         AlertDialog alertDialog = (AlertDialog) getDialog();
         alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(ThemeUtils.primaryAccentColor(getContext()));
         alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(ThemeUtils.primaryAccentColor(getContext()));
+        alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL)
+                .setTextColor(getResources().getColor(R.color.highlight_textColor_Warning));
     }
 
     /**
      * Public factory method to create new SharePasswordDialogFragment instances.
      *
-     * @param   file            OCFile bound to the public share that which password will be set or updated
-     * @param   createShare     When 'true', the request for password will be followed by the creation of a new
-     *                          public link; when 'false', a public share is assumed to exist, and the password
-     *                          is bound to it.
-     * @return                  Dialog ready to show.
+     * @param file        OCFile bound to the public share that which password will be set or updated
+     * @param createShare When 'true', the request for password will be followed by the creation of a new
+     *                    public link; when 'false', a public share is assumed to exist, and the password
+     *                    is bound to it.
+     * @return Dialog ready to show.
      */
     public static SharePasswordDialogFragment newInstance(OCFile file, boolean createShare) {
         SharePasswordDialogFragment frag = new SharePasswordDialogFragment();
@@ -80,11 +84,17 @@ public class SharePasswordDialogFragment extends DialogFragment
         return frag;
     }
 
+    @Nullable
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+        return super.onCreateView(inflater, container, savedInstanceState);
+    }
+
     @NonNull
     @Override
     public Dialog onCreateDialog(Bundle savedInstanceState) {
-        mFile = getArguments().getParcelable(ARG_FILE);
-        mCreateShare = getArguments().getBoolean(ARG_CREATE_SHARE, false);
+        file = getArguments().getParcelable(ARG_FILE);
+        createShare = getArguments().getBoolean(ARG_CREATE_SHARE, false);
 
         // Inflate the layout for the dialog
         LayoutInflater inflater = getActivity().getLayoutInflater();
@@ -92,7 +102,10 @@ public class SharePasswordDialogFragment extends DialogFragment
 
         // Setup layout
         EditText inputText = v.findViewById(R.id.share_password);
-        inputText.getBackground().setColorFilter(ThemeUtils.primaryAccentColor(getContext()), PorterDuff.Mode.SRC_ATOP);
+        inputText.getBackground().setColorFilter(
+                ThemeUtils.primaryAccentColor(getContext()),
+                PorterDuff.Mode.SRC_ATOP
+        );
         inputText.setText("");
         inputText.requestFocus();
 
@@ -102,19 +115,17 @@ public class SharePasswordDialogFragment extends DialogFragment
         builder.setView(v)
                 .setPositiveButton(R.string.common_ok, this)
                 .setNegativeButton(R.string.common_cancel, this)
+                .setNeutralButton(R.string.common_delete, this)
                 .setTitle(R.string.share_link_password_title);
         Dialog d = builder.create();
         d.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
         return d;
     }
 
-
     @Override
     public void onClick(DialogInterface dialog, int which) {
         if (which == AlertDialog.BUTTON_POSITIVE) {
-            String password =
-                    ((TextView)(getDialog().findViewById(R.id.share_password)))
-                        .getText().toString();
+            String password = ((TextView) (getDialog().findViewById(R.id.share_password))).getText().toString();
 
             if (password.length() <= 0) {
                 Snackbar.make(
@@ -125,16 +136,17 @@ public class SharePasswordDialogFragment extends DialogFragment
                 return;
             }
 
-            if (mCreateShare) {
-                // Share the file
-                ((FileActivity) getActivity()).getFileOperationsHelper().
-                        shareFileViaLink(mFile, password);
+            setPassword(createShare, file, password);
+        } else if (which == AlertDialog.BUTTON_NEUTRAL) {
+            setPassword(createShare, file, null);
+        }
+    }
 
-            } else {
-                // updat existing link
-                ((FileActivity) getActivity()).getFileOperationsHelper().
-                        setPasswordToShareViaLink(mFile, password);
-            }
+    private void setPassword(boolean createShare, OCFile file, String password) {
+        if (createShare) {
+            ((FileActivity) getActivity()).getFileOperationsHelper().shareFileViaLink(file, password);
+        } else {
+            ((FileActivity) getActivity()).getFileOperationsHelper().setPasswordToShareViaLink(file, password);
         }
     }
 }

+ 42 - 30
src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.java

@@ -28,6 +28,7 @@ import android.graphics.PorterDuff;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
 import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
 import android.support.v4.widget.SwipeRefreshLayout;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
@@ -143,31 +144,25 @@ public class FileDetailActivitiesFragment extends Fragment implements ActivityLi
 
         fetchAndSetData(null);
 
-        swipeListRefreshLayout.setOnRefreshListener(() -> {
-                    setLoadingMessage();
-                    if (swipeListRefreshLayout != null && swipeListRefreshLayout.isRefreshing()) {
-                        swipeListRefreshLayout.setRefreshing(false);
-                    }
-                    fetchAndSetData(null);
-                }
-        );
-
-        swipeEmptyListRefreshLayout.setOnRefreshListener(() -> {
-                    setLoadingMessage();
-                    if (swipeEmptyListRefreshLayout != null && swipeEmptyListRefreshLayout.isRefreshing()) {
-                        swipeEmptyListRefreshLayout.setRefreshing(false);
-                    }
-                    fetchAndSetData(null);
-                }
-        );
+        swipeListRefreshLayout.setOnRefreshListener(
+                () -> onRefreshListLayout(swipeListRefreshLayout));
+        swipeEmptyListRefreshLayout.setOnRefreshListener(
+                () -> onRefreshListLayout(swipeEmptyListRefreshLayout));
 
         return view;
     }
 
+    private void onRefreshListLayout(SwipeRefreshLayout refreshLayout) {
+        setLoadingMessage();
+        if (refreshLayout != null && refreshLayout.isRefreshing()) {
+            refreshLayout.setRefreshing(false);
+        }
+        fetchAndSetData(null);
+    }
+
     private void setLoadingMessage() {
         emptyContentHeadline.setText(R.string.file_list_loading);
         emptyContentMessage.setText("");
-
         emptyContentIcon.setVisibility(View.GONE);
         emptyContentProgressBar.setVisibility(View.VISIBLE);
     }
@@ -216,6 +211,11 @@ public class FileDetailActivitiesFragment extends Fragment implements ActivityLi
     private void fetchAndSetData(String pageUrl) {
         final Account currentAccount = AccountUtils.getCurrentOwnCloudAccount(MainApp.getAppContext());
         final Context context = MainApp.getAppContext();
+        final FragmentActivity activity = getActivity();
+
+        final SwipeRefreshLayout empty = swipeEmptyListRefreshLayout;
+        final SwipeRefreshLayout list = swipeListRefreshLayout;
+
 
         Thread t = new Thread(() -> {
             OwnCloudAccount ocAccount;
@@ -241,15 +241,15 @@ public class FileDetailActivitiesFragment extends Fragment implements ActivityLi
                     final ArrayList<Object> activities = (ArrayList) data.get(0);
                     nextPageUrl = (String) data.get(1);
 
-                    getActivity().runOnUiThread(() -> {
+                    activity.runOnUiThread(() -> {
                         populateList(activities, ownCloudClient, pageUrl == null);
                         if (activities.size() > 0) {
-                            swipeEmptyListRefreshLayout.setVisibility(View.GONE);
-                            swipeListRefreshLayout.setVisibility(View.VISIBLE);
+                            empty.setVisibility(View.GONE);
+                            list.setVisibility(View.VISIBLE);
                         } else {
                             setEmptyContent(noResultsHeadline, noResultsMessage);
-                            swipeListRefreshLayout.setVisibility(View.GONE);
-                            swipeEmptyListRefreshLayout.setVisibility(View.VISIBLE);
+                            list.setVisibility(View.GONE);
+                            empty.setVisibility(View.VISIBLE);
                         }
                         isLoadingActivities = false;
                     });
@@ -261,14 +261,13 @@ public class FileDetailActivitiesFragment extends Fragment implements ActivityLi
                         logMessage = noResultsMessage;
                     }
                     final String finalLogMessage = logMessage;
-                    getActivity().runOnUiThread(() -> {
-                        setEmptyContent(noResultsHeadline, finalLogMessage);
+                    activity.runOnUiThread(() -> {
+                        setErrorContent(finalLogMessage);
                         isLoadingActivities = false;
-                        //setIndeterminate(isLoadingActivities);
                     });
                 }
 
-                hideRefreshLayoutLoader();
+                hideRefreshLayoutLoader(activity);
             } catch (com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException e) {
                 Log_OC.e(TAG, "Account not found", e);
             } catch (IOException e) {
@@ -290,16 +289,30 @@ public class FileDetailActivitiesFragment extends Fragment implements ActivityLi
 
     private void setEmptyContent(String headline, String message) {
         if (emptyContentContainer != null && emptyContentMessage != null) {
+            emptyContentIcon.setImageDrawable(getContext().getResources().getDrawable(R.drawable.ic_activity_light_grey));
             emptyContentHeadline.setText(headline);
             emptyContentMessage.setText(message);
 
+            emptyContentMessage.setVisibility(View.VISIBLE);
+            emptyContentProgressBar.setVisibility(View.GONE);
+            emptyContentIcon.setVisibility(View.VISIBLE);
+        }
+    }
+
+    private void setErrorContent(String message) {
+        if (emptyContentContainer != null && emptyContentMessage != null) {
+            emptyContentHeadline.setText(R.string.common_error);
+            emptyContentIcon.setImageDrawable(getContext().getResources().getDrawable(R.drawable.ic_alert_octagon));
+            emptyContentMessage.setText(message);
+
+            emptyContentMessage.setVisibility(View.VISIBLE);
             emptyContentProgressBar.setVisibility(View.GONE);
             emptyContentIcon.setVisibility(View.VISIBLE);
         }
     }
 
-    private void hideRefreshLayoutLoader() {
-        getActivity().runOnUiThread(() -> {
+    private void hideRefreshLayoutLoader(FragmentActivity activity) {
+        activity.runOnUiThread(() -> {
             if (swipeListRefreshLayout != null) {
                 swipeListRefreshLayout.setRefreshing(false);
             }
@@ -307,7 +320,6 @@ public class FileDetailActivitiesFragment extends Fragment implements ActivityLi
                 swipeEmptyListRefreshLayout.setRefreshing(false);
             }
             isLoadingActivities = false;
-            //setIndeterminate(isLoadingActivities);
         });
     }
 

+ 217 - 255
src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java

@@ -3,8 +3,10 @@
  *
  *   @author Bartek Przybylski
  *   @author David A. Velasco
+ *   @author Andy Scherzinger
  *   Copyright (C) 2011  Bartek Przybylski
  *   Copyright (C) 2016 ownCloud Inc.
+ *   Copyright (C) 2018 Andy Scherzinger
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -17,16 +19,15 @@
  *
  *   You should have received a copy of the GNU General Public License
  *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
  */
 package com.owncloud.android.ui.fragment;
 
 import android.accounts.Account;
+import android.content.Context;
 import android.graphics.Bitmap;
-import android.os.Build;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
-import android.support.design.widget.Snackbar;
+import android.support.annotation.Nullable;
 import android.support.design.widget.TabLayout;
 import android.support.v4.view.ViewPager;
 import android.view.LayoutInflater;
@@ -35,6 +36,7 @@ import android.view.MenuItem;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
+import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.PopupMenu;
 import android.widget.ProgressBar;
@@ -62,27 +64,64 @@ import com.owncloud.android.utils.ThemeUtils;
 
 import java.lang.ref.WeakReference;
 
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.Unbinder;
 
 /**
  * This Fragment is used to display the details about a file.
  */
 public class FileDetailFragment extends FileFragment implements OnClickListener {
+    private static final String TAG = FileDetailFragment.class.getSimpleName();
+    public static final String FTAG_CONFIRMATION = "REMOVE_CONFIRMATION_FRAGMENT";
+    public static final String FTAG_RENAME_FILE = "RENAME_FILE_FRAGMENT";
+
+    private static final String ARG_FILE = "FILE";
+    private static final String ARG_ACCOUNT = "ACCOUNT";
+    private static final String ARG_ACTIVE_TAB = "TAB";
+
+    @Nullable @BindView(R.id.progressBlock)
+    View downloadProgressContainer;
+
+    @Nullable @BindView(R.id.cancelBtn)
+    ImageButton cancelButton;
+
+    @Nullable @BindView(R.id.progressBar)
+    ProgressBar progressBar;
+
+    @Nullable @BindView(R.id.progressText)
+    TextView progressText;
+
+    @Nullable @BindView(R.id.filename)
+    TextView fileName;
+
+    @Nullable @BindView(R.id.size)
+    TextView fileSize;
+
+    @Nullable @BindView(R.id.modified)
+    TextView fileModifiedTimestamp;
+
+    @Nullable @BindView(R.id.favorite)
+    ImageView favoriteIcon;
+
+    @Nullable @BindView(R.id.overflow_menu)
+    ImageView overflowMenu;
+
+    @Nullable @BindView(R.id.tab_layout)
+    TabLayout tabLayout;
+
+    @Nullable @BindView(R.id.pager)
+    ViewPager viewPager;
 
     private int layout;
     private View view;
-    private ImageView previewImage;
-    private ProgressBar toolbarProgressBar;
     private boolean previewLoaded;
     private Account account;
+    private Unbinder unbinder;
 
     public ProgressListener progressListener;
-
-    private static final String TAG = FileDetailFragment.class.getSimpleName();
-    public static final String FTAG_CONFIRMATION = "REMOVE_CONFIRMATION_FRAGMENT";
-    public static final String FTAG_RENAME_FILE = "RENAME_FILE_FRAGMENT";
-
-    private static final String ARG_FILE = "FILE";
-    private static final String ARG_ACCOUNT = "ACCOUNT";
+    private ToolbarActivity activity;
+    private int activeTab;
 
     /**
      * Public factory method to create new FileDetailFragment instances.
@@ -102,6 +141,26 @@ public class FileDetailFragment extends FileFragment implements OnClickListener
         return frag;
     }
 
+    /**
+     * Public factory method to create new FileDetailFragment instances.
+     *
+     * When 'fileToDetail' or 'ocAccount' are null, creates a dummy layout (to use when a file wasn't tapped before).
+     *
+     * @param fileToDetail      An {@link OCFile} to show in the fragment
+     * @param account           An ownCloud account; needed to start downloads
+     *                          @param activeTab to be active tab
+     * @return New fragment with arguments set
+     */
+    public static FileDetailFragment newInstance(OCFile fileToDetail, Account account, int activeTab) {
+        FileDetailFragment frag = new FileDetailFragment();
+        Bundle args = new Bundle();
+        args.putParcelable(ARG_FILE, fileToDetail);
+        args.putParcelable(ARG_ACCOUNT, account);
+        args.putInt(ARG_ACTIVE_TAB, activeTab);
+        frag.setArguments(args);
+        return frag;
+    }
+
     /**
      * Creates an empty details fragment.
      * 
@@ -111,25 +170,38 @@ public class FileDetailFragment extends FileFragment implements OnClickListener
     public FileDetailFragment() {
         super();
         account = null;
-        layout = R.layout.file_details_empty;
+        layout = R.layout.empty_list;
         progressListener = null;
     }
 
+    /**
+     * return the reference to the file detail sharing fragment to communicate with it.
+     *
+     * @return reference to the {@link FileDetailSharingFragment}
+     */
+    public FileDetailSharingFragment getFileDetailSharingFragment() {
+        return ((FileDetailTabAdapter)viewPager.getAdapter()).getFileDetailSharingFragment();
+    }
+
     @Override
     public void onResume() {
         super.onResume();
 
-        if (previewImage != null && MimeTypeUtil.isImage(getFile()) && previewLoaded) {
+        if (getFile()!= null && MimeTypeUtil.isImage(getFile()) && previewLoaded) {
             activatePreviewImage();
         }
     }
 
     private void activatePreviewImage() {
-        previewImage.setVisibility(View.VISIBLE);
-        toolbarProgressBar.setVisibility(View.GONE);
-        ((ToolbarActivity) getActivity()).getSupportActionBar().setTitle(null);
-        ((ToolbarActivity) getActivity()).getSupportActionBar().setBackgroundDrawable(null);
-        makeStatusBarBlack();
+        if (activity != null) {
+            activity.setPreviewImageVisibility(View.VISIBLE);
+            activity.setProgressBarVisibility(View.GONE);
+            ThemeUtils.setStatusBarColor(activity, activity.getResources().getColor(R.color.black));
+            if (activity.getSupportActionBar() != null) {
+                activity.getSupportActionBar().setTitle(null);
+                activity.getSupportActionBar().setBackgroundDrawable(null);
+            }
+        }
     }
 
     @Override
@@ -139,105 +211,50 @@ public class FileDetailFragment extends FileFragment implements OnClickListener
     }
 
     @Override
-    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
+    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
 
         setFile(getArguments().getParcelable(ARG_FILE));
         account = getArguments().getParcelable(ARG_ACCOUNT);
+        activeTab = getArguments().getInt(ARG_ACTIVE_TAB, 0);
 
         if (savedInstanceState != null) {
             setFile(savedInstanceState.getParcelable(FileActivity.EXTRA_FILE));
             account = savedInstanceState.getParcelable(FileActivity.EXTRA_ACCOUNT);
         }
 
-        toolbarProgressBar = getActivity().findViewById(R.id.progressBar);
-
         if (getFile() != null && account != null) {
             layout = R.layout.file_details_fragment;
         }
 
         view = inflater.inflate(layout, null);
-        
-        if (layout == R.layout.file_details_fragment) {
-            ProgressBar progressBar = view.findViewById(R.id.fdProgressBar);
-            ThemeUtils.colorHorizontalProgressBar(progressBar, ThemeUtils.primaryAccentColor(getContext()));
-            progressListener = new ProgressListener(progressBar);
-            view.findViewById(R.id.fdCancelBtn).setOnClickListener(this);
-            view.findViewById(R.id.fdFavorite).setOnClickListener(this);
-            view.findViewById(R.id.overflow_menu).setOnClickListener(this);
-            previewImage = getActivity().findViewById(R.id.preview_image);
-            // TODO use whenever we switch to use glide for preview images
-            /*
-            if (getFile() != null && account != null && MimeTypeUtil.isImage(getFile())) {
-                setHeaderImage();
-            }
-             */
-        }
+        unbinder = ButterKnife.bind(this, view);
 
-        updateFileDetails(false, false);
         return view;
     }
-    // TODO use whenever we switch to use glide for preview images
-    /*
-    private void setHeaderImage() {
-        if (mContainerActivity.getStorageManager().getCapability(account.name)
-                .getServerBackground() != null && previewImage != null) {
-
-            String background = mContainerActivity.getStorageManager().getCapability(account.name).getServerBackground();
-
-            int primaryColor = ThemeUtils.primaryColor(account, getContext());
-
-            if (URLUtil.isValidUrl(background)) {
-                // background image
-                SimpleTarget target = new SimpleTarget<Drawable>() {
-                    @Override
-                    public void onResourceReady(Drawable resource, GlideAnimation glideAnimation) {
-                        Drawable[] drawables = {new ColorDrawable(primaryColor), resource};
-                        LayerDrawable layerDrawable = new LayerDrawable(drawables);
-                        previewImage.setImageDrawable(layerDrawable);
-                        previewImage.setVisibility(View.VISIBLE);
-                        ((ToolbarActivity) getActivity()).getSupportActionBar().setTitle(null);
-                        ((ToolbarActivity) getActivity()).getSupportActionBar().setBackgroundDrawable(null);
-                        toolbarProgressBar.setVisibility(View.GONE);
-                        previewLoaded = true;
-                    }
 
-                    @Override
-                    public void onLoadFailed(Exception e, Drawable errorDrawable) {
-                        previewImage.setVisibility(View.GONE);
-                        toolbarProgressBar.setVisibility(View.VISIBLE);
-                    }
-                };
-
-                Glide.with(this)
-                        .load(background)
-                        .centerCrop()
-                        .placeholder(R.drawable.background)
-                        .error(R.drawable.background)
-                        .crossFade()
-                        .into(target);
-            } else {
-                // hide image
-                previewImage.setVisibility(View.GONE);
-                toolbarProgressBar.setVisibility(View.VISIBLE);
-            }
+    @Override
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+        if (getFile() != null && account != null) {
+            ThemeUtils.colorHorizontalProgressBar(progressBar, ThemeUtils.primaryAccentColor(getContext()));
+            progressListener = new ProgressListener(progressBar);
+            cancelButton.setOnClickListener(this);
+            favoriteIcon.setOnClickListener(this);
+            overflowMenu.setOnClickListener(this);
+
+            updateFileDetails(false, false);
         }
     }
-    */
 
     public void onOverflowIconClicked(View view) {
         PopupMenu popup = new PopupMenu(getActivity(), view);
-        popup.inflate(R.menu.file_actions_menu);
+        popup.inflate(R.menu.file_details_actions_menu);
         prepareOptionsMenu(popup.getMenu());
 
-        popup.setOnMenuItemClickListener(item -> {
-            return optionsItemSelected(item);
-        });
+        popup.setOnMenuItemClickListener(this::optionsItemSelected);
         popup.show();
     }
 
-    private void setupViewPager(View view) {
-        TabLayout tabLayout = view.findViewById(R.id.tab_layout);
+    private void setupViewPager() {
         tabLayout.removeAllTabs();
 
         tabLayout.addTab(tabLayout.newTab().setText(R.string.drawer_item_activities));
@@ -246,7 +263,6 @@ public class FileDetailFragment extends FileFragment implements OnClickListener
         tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
         tabLayout.setSelectedTabIndicatorColor(ThemeUtils.primaryAccentColor(getContext()));
 
-        final ViewPager viewPager = view.findViewById(R.id.pager);
         final FileDetailTabAdapter adapter = new FileDetailTabAdapter
                 (getFragmentManager(), getFile(), account);
         viewPager.setAdapter(adapter);
@@ -267,6 +283,8 @@ public class FileDetailFragment extends FileFragment implements OnClickListener
                 // unused at the moment
             }
         });
+
+        tabLayout.getTabAt(activeTab).select();
     }
 
     @Override
@@ -285,17 +303,32 @@ public class FileDetailFragment extends FileFragment implements OnClickListener
     @Override
     public void onStop() {
         leaveTransferProgress();
-        if (previewImage != null) {
-            previewImage.setVisibility(View.GONE);
-            toolbarProgressBar.setVisibility(View.VISIBLE);
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-                getActivity().getWindow().setStatusBarColor(ThemeUtils.primaryDarkColor(getContext()));
-            }
+
+        if(activity != null) {
+            activity.setPreviewImageVisibility(View.GONE);
+            activity.setProgressBarVisibility(View.VISIBLE);
+            activity.setupToolbar();
         }
 
         super.onStop();
     }
 
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        if (context instanceof ToolbarActivity) {
+            activity = (ToolbarActivity) context;
+        } else {
+            activity = null;
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        unbinder.unbind();
+    }
+
     @Override
     public View getView() {
         return super.getView() == null ? view : super.getView();
@@ -320,44 +353,23 @@ public class FileDetailFragment extends FileFragment implements OnClickListener
             mf.filter(menu, true);
         }
 
-        // restriction for this fragment
-        FileMenuFilter.hideMenuItems(
-                menu.findItem(R.id.action_see_details),
-                menu.findItem(R.id.action_select_all),
-                menu.findItem(R.id.action_move),
-                menu.findItem(R.id.action_copy),
-                menu.findItem(R.id.action_favorite),
-                menu.findItem(R.id.action_unset_favorite),
-                menu.findItem(R.id.action_search)
-        );
+        if (getFile().isFolder()) {
+            FileMenuFilter.hideMenuItems(menu.findItem(R.id.action_send_file));
+            FileMenuFilter.hideMenuItems(menu.findItem(R.id.action_sync_file));
+        }
 
         // dual pane restrictions
         if (!getResources().getBoolean(R.bool.large_land_layout)){
-            FileMenuFilter.hideMenuItems(
-                    menu.findItem(R.id.action_switch_view),
-                    menu.findItem(R.id.action_sync_account),
-                    menu.findItem(R.id.action_sort)
-            );
+            FileMenuFilter.hideMenuItems(menu.findItem(R.id.action_sync_account));
         }
 
-        // share restrictions
-        if (getFile().isSharedWithMe() && !getFile().canReshare()) {
-            FileMenuFilter.hideMenuItems(menu.findItem(R.id.action_send_share_file));
-        }
+
     }
 
-    public boolean optionsItemSelected(MenuItem item) {
+    private boolean optionsItemSelected(MenuItem item) {
         switch (item.getItemId()) {
-            case R.id.action_send_share_file: {
-                if(getFile().isSharedWithMe() && !getFile().canReshare()){
-                    Snackbar.make(getView(),
-                            R.string.resharing_is_not_allowed,
-                            Snackbar.LENGTH_LONG
-                    )
-                            .show();
-                } else {
-                    mContainerActivity.getFileOperationsHelper().sendShareFile(getFile());
-                }
+            case R.id.action_send_file: {
+                mContainerActivity.getFileOperationsHelper().sendShareFile(getFile(), true);
                 return true;
             }
             case R.id.action_open_file_with: {
@@ -391,6 +403,14 @@ public class FileDetailFragment extends FileFragment implements OnClickListener
                 mContainerActivity.getFileOperationsHelper().toggleOfflineFile(getFile(), false);
                 return true;
             }
+            case R.id.action_encrypted: {
+                // TODO implement or remove
+                return true;
+            }
+            case R.id.action_unset_encrypted: {
+                // TODO implement or remove
+                return true;
+            }
             default:
                 return super.onOptionsItemSelected(item);
         }
@@ -399,22 +419,17 @@ public class FileDetailFragment extends FileFragment implements OnClickListener
     @Override
     public void onClick(View v) {
         switch (v.getId()) {
-            case R.id.fdCancelBtn: {
+            case R.id.cancelBtn: {
                 ((FileDisplayActivity) mContainerActivity).cancelTransference(getFile());
                 break;
             }
-            case R.id.fdFavorite: {
+            case R.id.favorite: {
                 if (getFile().getIsFavorite()) {
-                    ((ImageView)getView().findViewById(R.id.fdFavorite)).
-                            setImageDrawable(getResources()
-                                    .getDrawable(R.drawable.ic_star_outline));
                     mContainerActivity.getFileOperationsHelper().toggleFavoriteFile(getFile(), false);
                 } else {
-                    ((ImageView)getView().findViewById(R.id.fdFavorite))
-                            .setImageDrawable(getResources()
-                                    .getDrawable(R.drawable.ic_star));
                     mContainerActivity.getFileOperationsHelper().toggleFavoriteFile(getFile(), true);
                 }
+                setFavoriteIconStatus(!getFile().getIsFavorite());
                 break;
             }
             case R.id.overflow_menu: {
@@ -433,7 +448,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener
      * @return True when the fragment was created with the empty layout.
      */
     public boolean isEmpty() {
-        return (layout == R.layout.file_details_empty || getFile() == null || account == null);
+        return (layout == R.layout.empty_list || getFile() == null || account == null);
     }
 
     /**
@@ -471,16 +486,15 @@ public class FileDetailFragment extends FileFragment implements OnClickListener
             OCFile file = getFile();
 
             // set file details
-            setFilename(file.getFileName());
-            setFiletype(file);
-            setFilesize(file.getFileLength());
-            setTimeModified(file.getModificationTimestamp());
-
-            if (file.getIsFavorite()) {
-                ((ImageView)getView().findViewById(R.id.fdFavorite)).setImageDrawable(getResources().getDrawable(R.drawable.ic_star));
+            if (MimeTypeUtil.isImage(file)) {
+                fileName.setText(file.getFileName());
             } else {
-                ((ImageView)getView().findViewById(R.id.fdFavorite)).setImageDrawable(getResources().getDrawable(R.drawable.ic_star_outline));
+                fileName.setVisibility(View.GONE);
             }
+            fileSize.setText(DisplayUtils.bytesToHumanReadable(file.getFileLength()));
+            fileModifiedTimestamp.setText(DisplayUtils.getRelativeTimestamp(getContext(), file.getModificationTimestamp()));
+            setFilePreview(file);
+            setFavoriteIconStatus(file.getIsFavorite());
 
             // configure UI for depending upon local state of the file
             FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
@@ -502,11 +516,19 @@ public class FileDetailFragment extends FileFragment implements OnClickListener
             }
         }
 
-        setupViewPager(getView());
+        setupViewPager();
 
         getView().invalidate();
     }
 
+    private void setFavoriteIconStatus(boolean isFavorite) {
+        if (isFavorite) {
+            favoriteIcon.setImageDrawable(getResources().getDrawable(R.drawable.ic_star));
+        } else {
+            favoriteIcon.setImageDrawable(getResources().getDrawable(R.drawable.ic_star_outline));
+        }
+    }
+
     /**
      * Checks if the fragment is ready to show details of a OCFile
      *
@@ -517,124 +539,68 @@ public class FileDetailFragment extends FileFragment implements OnClickListener
     }
 
     /**
-     * Updates the filename in view
+     * Updates the file preview if possible
      *
-     * @param filename to set
+     * @param file a {@link OCFile} to be previewed
      */
-    private void setFilename(String filename) {
-        TextView tv = getView().findViewById(R.id.fdFilename);
-        if (tv != null) {
-            tv.setText(filename);
-        }
-    }
+    private void setFilePreview(OCFile file) {
+        Bitmap resizedImage;
 
-    /**
-     * Updates the MIME type in view
-     * @param file : An {@link OCFile}
-     */
-    private void setFiletype(OCFile file) {
-        ImageView iv = getView().findViewById(R.id.fdIcon);
-        View v = getView().findViewById(R.id.fdIcon_divider);
+        if (MimeTypeUtil.isImage(file) && activity != null) {
+            String tagId = String.valueOf(ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + getFile().getRemoteId());
+            resizedImage = ThumbnailsCacheManager.getBitmapFromDiskCache(tagId);
 
-        if (iv != null) {
-            iv.setTag(file.getFileId());
-            // Name of the file, to deduce the icon to use in case the MIME type is not precise enough
-            iv.setImageDrawable(MimeTypeUtil.getFileTypeIcon(file.getMimetype(), file.getFileName(), account,
-                    getContext()));
+            if (resizedImage != null && !file.needsUpdateThumbnail()) {
+                activity.setPreviewImageBitmap(resizedImage);
+                activatePreviewImage();
+                previewLoaded = true;
+            } else {
+                // show thumbnail while loading resized image
+                Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(
+                        String.valueOf(ThumbnailsCacheManager.PREFIX_THUMBNAIL + getFile().getRemoteId()));
 
-            Bitmap resizedImage;
+                if (thumbnail != null) {
+                    activity.setPreviewImageBitmap(thumbnail);
+                } else {
+                    thumbnail = ThumbnailsCacheManager.mDefaultImg;
+                }
 
-            if (MimeTypeUtil.isImage(file)) {
-                String tagId = String.valueOf(ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + getFile().getRemoteId());
-                resizedImage = ThumbnailsCacheManager.getBitmapFromDiskCache(tagId);
+                // generate new resized image
+                if (ThumbnailsCacheManager.cancelPotentialThumbnailWork(getFile(), activity.getPreviewImageView()) &&
+                        mContainerActivity.getStorageManager() != null) {
+                    final ThumbnailsCacheManager.ResizedImageGenerationTask task =
+                            new ThumbnailsCacheManager.ResizedImageGenerationTask(FileDetailFragment.this,
+                                    activity.getPreviewImageView(),
+                                    mContainerActivity.getStorageManager(),
+                                    mContainerActivity.getStorageManager().getAccount());
+
+                    if (resizedImage == null) {
+                        resizedImage = thumbnail;
+                    }
+
+                    final ThumbnailsCacheManager.AsyncResizedImageDrawable asyncDrawable =
+                            new ThumbnailsCacheManager.AsyncResizedImageDrawable(
+                                    MainApp.getAppContext().getResources(),
+                                    resizedImage,
+                                    task
+                            );
 
-                if (resizedImage != null && !file.needsUpdateThumbnail()) {
-                    iv.setVisibility(View.GONE);
-                    v.setVisibility(View.GONE);
-                    previewImage.setImageBitmap(resizedImage);
+                    activity.setPreviewImageDrawable(asyncDrawable);
                     activatePreviewImage();
                     previewLoaded = true;
-                } else {
-                    // show thumbnail while loading resized image
-                    Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(
-                            String.valueOf(ThumbnailsCacheManager.PREFIX_THUMBNAIL + getFile().getRemoteId()));
-
-                    if (thumbnail != null) {
-                        iv.setImageBitmap(thumbnail);
-                    } else {
-                        thumbnail = ThumbnailsCacheManager.mDefaultImg;
-                    }
-
-                    // generate new resized image
-                    if (ThumbnailsCacheManager.cancelPotentialThumbnailWork(getFile(), iv) &&
-                            mContainerActivity.getStorageManager() != null) {
-                        final ThumbnailsCacheManager.ResizedImageGenerationTask task =
-                                new ThumbnailsCacheManager.ResizedImageGenerationTask(FileDetailFragment.this,
-                                        iv,
-                                        mContainerActivity.getStorageManager(),
-                                        mContainerActivity.getStorageManager().getAccount());
-                        if (resizedImage == null) {
-                            resizedImage = thumbnail;
-                        }
-                        final ThumbnailsCacheManager.AsyncResizedImageDrawable asyncDrawable =
-                                new ThumbnailsCacheManager.AsyncResizedImageDrawable(
-                                        MainApp.getAppContext().getResources(),
-                                        resizedImage,
-                                        task
-                                );
-                        iv.setVisibility(View.GONE);
-                        v.setVisibility(View.GONE);
-                        previewImage.setImageDrawable(asyncDrawable);
-                        activatePreviewImage();
-                        previewLoaded = true;
-                        task.execute(getFile());
-                    }
+                    task.execute(getFile());
                 }
-            } else {
-                iv.setImageDrawable(MimeTypeUtil.getFileTypeIcon(file.getMimetype(), file.getFileName(), account,
-                        getContext()));
             }
         }
     }
 
-    private void makeStatusBarBlack() {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-            getActivity().getWindow().setStatusBarColor(getResources().getColor(R.color.black));
-        }
-    }
-
-    /**
-     * Updates the file size in view
-     *
-     * @param fileSize in bytes to set
-     */
-    private void setFilesize(long fileSize) {
-        TextView tv = getView().findViewById(R.id.fdSize);
-        if (tv != null) {
-            tv.setText(DisplayUtils.bytesToHumanReadable(fileSize));
-        }
-    }
-
-    /**
-     * Updates the time that the file was last modified
-     *
-     * @param milliseconds Unix time to set
-     */
-    private void setTimeModified(long milliseconds) {
-        TextView tv = getView().findViewById(R.id.fdModified);
-        if (tv != null) {
-            tv.setText(DisplayUtils.unixTimeToHumanReadable(milliseconds));
-        }
-    }
-
     /**
      * Enables or disables buttons for a file being downloaded
      */
     private void setButtonsForTransferring() {
         if (!isEmpty()) {
             // show the progress bar for the transfer
-            getView().findViewById(R.id.fdProgressBlock).setVisibility(View.VISIBLE);
-            TextView progressText = getView().findViewById(R.id.fdProgressText);
+            downloadProgressContainer.setVisibility(View.VISIBLE);
             progressText.setVisibility(View.VISIBLE);
             FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
             FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
@@ -656,9 +622,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener
     private void setButtonsForDown() {
         if (!isEmpty()) {
             // hides the progress bar
-            getView().findViewById(R.id.fdProgressBlock).setVisibility(View.GONE);
-            TextView progressText = getView().findViewById(R.id.fdProgressText);
-            progressText.setVisibility(View.GONE);
+            downloadProgressContainer.setVisibility(View.GONE);
         }
     }
 
@@ -668,9 +632,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener
     private void setButtonsForRemote() {
         if (!isEmpty()) {
             // hides the progress bar
-            getView().findViewById(R.id.fdProgressBlock).setVisibility(View.GONE);
-            TextView progressText = getView().findViewById(R.id.fdProgressText);
-            progressText.setVisibility(View.GONE);
+            downloadProgressContainer.setVisibility(View.GONE);
         }
     }
 
@@ -706,25 +668,25 @@ public class FileDetailFragment extends FileFragment implements OnClickListener
      * Helper class responsible for updating the progress bar shown for file downloading.
      */
     private class ProgressListener implements OnDatatransferProgressListener {
-        private int mLastPercent = 0;
-        private WeakReference<ProgressBar> mProgressBar;
+        private int lastPercent = 0;
+        private WeakReference<ProgressBar> progressBarReference;
 
         ProgressListener(ProgressBar progressBar) {
-            mProgressBar = new WeakReference<>(progressBar);
+            progressBarReference = new WeakReference<>(progressBar);
         }
 
         @Override
         public void onTransferProgress(long progressRate, long totalTransferredSoFar,
                                        long totalToTransfer, String filename) {
             int percent = (int)(100.0*((double)totalTransferredSoFar)/((double)totalToTransfer));
-            if (percent != mLastPercent) {
-                ProgressBar pb = mProgressBar.get();
+            if (percent != lastPercent) {
+                ProgressBar pb = progressBarReference.get();
                 if (pb != null) {
                     pb.setProgress(percent);
                     pb.postInvalidate();
                 }
             }
-            mLastPercent = percent;
+            lastPercent = percent;
         }
     }
 }

+ 348 - 63
src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java

@@ -21,37 +21,87 @@
 package com.owncloud.android.ui.fragment;
 
 import android.accounts.Account;
+import android.app.SearchManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.net.Uri;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
+import android.support.design.widget.Snackbar;
 import android.support.v4.app.Fragment;
+import android.support.v7.widget.AppCompatCheckBox;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.SearchView;
 import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ImageView;
-import android.widget.ListAdapter;
-import android.widget.ListView;
+import android.widget.LinearLayout;
+import android.widget.PopupMenu;
 import android.widget.TextView;
 
 import com.owncloud.android.R;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.resources.shares.OCShare;
+import com.owncloud.android.lib.resources.shares.SharePermissionsBuilder;
+import com.owncloud.android.lib.resources.shares.ShareType;
+import com.owncloud.android.lib.resources.status.OCCapability;
 import com.owncloud.android.ui.activity.FileActivity;
 import com.owncloud.android.ui.adapter.UserListAdapter;
+import com.owncloud.android.ui.decoration.SimpleListItemDividerDecoration;
+import com.owncloud.android.ui.dialog.ExpirationDatePickerDialogFragment;
+import com.owncloud.android.ui.dialog.SharePasswordDialogFragment;
+import com.owncloud.android.ui.fragment.util.FileDetailSharingFragmentHelper;
 import com.owncloud.android.utils.ThemeUtils;
 
 import java.util.ArrayList;
 
-public class FileDetailSharingFragment  extends Fragment {
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
+import butterknife.Unbinder;
+
+public class FileDetailSharingFragment extends Fragment implements UserListAdapter.ShareeListAdapterListener {
 
     private static final String ARG_FILE = "FILE";
     private static final String ARG_ACCOUNT = "ACCOUNT";
 
     // to show share with users/groups info
-    private ArrayList<OCShare> mShares;
+    private ArrayList<OCShare> shares;
 
     private OCFile file;
     private Account account;
+    private OCCapability capabilities;
+    private OCShare publicShare;
+
+    private Unbinder unbinder;
+
+    @BindView(R.id.searchView)
+    SearchView searchView;
+
+    @BindView(R.id.shareUsersList)
+    RecyclerView usersList;
+
+    @BindView(R.id.shareNoUsers)
+    TextView noList;
+
+    @BindView(R.id.share_by_link)
+    AppCompatCheckBox shareByLink;
+
+    @BindView(R.id.overflow_menu_share_link)
+    ImageView overflowMenuShareLink;
+
+    @BindView(R.id.share_by_link_allow_editing)
+    AppCompatCheckBox shareByLinkAllowEditing;
+
+    @BindView(R.id.share_by_link_container)
+    LinearLayout shareByLinkContainer;
 
     public static FileDetailSharingFragment newInstance(OCFile file, Account account) {
         FileDetailSharingFragment fragment = new FileDetailSharingFragment();
@@ -63,9 +113,15 @@ public class FileDetailSharingFragment  extends Fragment {
     }
 
     @Override
-    public View onCreateView(@NonNull LayoutInflater inflater,
-                             ViewGroup container,
-                             Bundle savedInstanceState) {
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        refreshCapabilitiesFromDB();
+        refreshPublicShareFromDB();
+    }
+
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
 
         file = getArguments().getParcelable(ARG_FILE);
         account = getArguments().getParcelable(ARG_ACCOUNT);
@@ -76,102 +132,331 @@ public class FileDetailSharingFragment  extends Fragment {
         }
 
         View view = inflater.inflate(R.layout.file_details_sharing_fragment, container, false);
+        unbinder = ButterKnife.bind(this, view);
 
-        setupView(view);
+        setupView();
 
         return view;
     }
 
-    private void setupView(View view) {
-        ((TextView)view.findViewById(R.id.fdShareTitle)).setTextColor(ThemeUtils.primaryAccentColor(getContext()));
-        ((TextView)view.findViewById(R.id.fdShareWithUsersTitle)).setTextColor(ThemeUtils.primaryAccentColor(getContext()));
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        unbinder.unbind();
+    }
 
-        setShareByLinkInfo(file.isSharedViaLink(), view);
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        if (!(getActivity() instanceof FileActivity)) {
+            throw new IllegalArgumentException("Calling activity must be of type FileActivity");
+        }
+    }
 
-        setShareWithUserInfo(view);
+    private void setupView() {
+        setShareByLinkInfo(file.isSharedViaLink());
+        setShareWithUserInfo();
+        FileDetailSharingFragmentHelper.setupSearchView(
+                (SearchManager) getActivity().getSystemService(Context.SEARCH_SERVICE),
+                searchView,
+                getActivity().getComponentName()
+        );
     }
 
     /**
-     * Updates Share by link data
+     * Updates Share by link UI
      *
      * @param isShareByLink flag is share by link is enable
      */
-    private void setShareByLinkInfo(boolean isShareByLink, View view) {
-        TextView tv = view.findViewById(R.id.fdSharebyLink);
-        if (tv != null) {
-            tv.setText(isShareByLink ? R.string.filedetails_share_link_enable :
-                    R.string.filedetails_share_link_disable);
-        }
-        ImageView linkIcon = view.findViewById(R.id.fdShareLinkIcon);
-        if (linkIcon != null) {
-            linkIcon.setVisibility(isShareByLink ? View.VISIBLE : View.GONE);
+    public void setShareByLinkInfo(boolean isShareByLink) {
+        shareByLink.setChecked(isShareByLink);
+        int accentColor = ThemeUtils.primaryAccentColor(getContext());
+        ThemeUtils.tintCheckbox(shareByLink, accentColor);
+        ThemeUtils.tintCheckbox(shareByLinkAllowEditing, accentColor);
+        setLinkDetailVisible(isShareByLink);
+    }
+
+    private void setLinkDetailVisible(boolean visible) {
+        if (visible) {
+            if (file.isFolder()) {
+                shareByLinkAllowEditing.setVisibility(View.VISIBLE);
+            } else {
+                shareByLinkAllowEditing.setVisibility(View.INVISIBLE);
+            }
+            overflowMenuShareLink.setVisibility(View.VISIBLE);
+        } else {
+            shareByLinkAllowEditing.setVisibility(View.INVISIBLE);
+            overflowMenuShareLink.setVisibility(View.INVISIBLE);
         }
     }
 
     /**
      * Update Share With data
      */
-    private void setShareWithUserInfo(View view){
+    public void setShareWithUserInfo() {
         // Get Users and Groups
         if (((FileActivity) getActivity()).getStorageManager() != null) {
             FileDataStorageManager fileDataStorageManager = ((FileActivity) getActivity()).getStorageManager();
-            mShares = fileDataStorageManager.getSharesWithForAFile(
-                    file.getRemotePath(),account.name
-            );
+
+            shares = fileDataStorageManager.getSharesWithForAFile(file.getRemotePath(), account.name);
 
             // Update list of users/groups
-            updateListOfUserGroups(view);
+            updateListOfUserGroups();
         }
     }
 
-    private void updateListOfUserGroups(View view) {
-        // Update list of users/groups
+    private void updateListOfUserGroups() {
         // TODO Refactoring: create a new {@link ShareUserListAdapter} instance with every call should not be needed
-        UserListAdapter mUserGroupsAdapter = new UserListAdapter(
-                getActivity().getApplicationContext(),
-                R.layout.share_user_item, mShares
-        );
 
-        // Show data
-        ListView usersList = view.findViewById(R.id.fdshareUsersList);
-
-        // No data
-        TextView noList = view.findViewById(R.id.fdShareNoUsers);
-
-        if (mShares.size() > 0) {
+        if (shares.size() > 0) {
             usersList.setVisibility(View.VISIBLE);
-            usersList.setAdapter(mUserGroupsAdapter);
+            usersList.setAdapter(new UserListAdapter(getActivity().getSupportFragmentManager(),
+                    getActivity().getApplicationContext(), shares, account, file, this));
+            usersList.setLayoutManager(new LinearLayoutManager(getContext()));
+            usersList.addItemDecoration(new SimpleListItemDividerDecoration(getContext()));
             noList.setVisibility(View.GONE);
-            setListViewHeightBasedOnChildren(usersList);
-
         } else {
             usersList.setVisibility(View.GONE);
             noList.setVisibility(View.VISIBLE);
         }
     }
 
+    @OnClick(R.id.share_by_link)
+    public void toggleShareByLink() {
+        if (shareByLink.isChecked()) {
+            if (capabilities != null &&
+                    capabilities.getFilesSharingPublicPasswordEnforced().isTrue()) {
+                // password enforced by server, request to the user before trying to create
+                requestPasswordForShareViaLink(true);
+
+            } else {
+                // create without password if not enforced by server or we don't know if enforced;
+                ((FileActivity) getActivity()).getFileOperationsHelper().shareFileViaLink(file, null);
+            }
+
+        } else {
+            ((FileActivity) getActivity()).getFileOperationsHelper().unshareFileViaLink(file);
+        }
+    }
+
+    @OnClick(R.id.share_link_label)
+    public void showSendLinkTo() {
+        if (file.isSharedViaLink()) {
+            ((FileActivity) getActivity()).getFileOperationsHelper().getFileWithLink(file);
+        }
+    }
+
+    @OnClick(R.id.share_by_link_allow_editing)
+    public void toggleShareLinkAllowEditing() {
+        if (file.isSharedViaLink()) {
+            ((FileActivity) getActivity()).getFileOperationsHelper()
+                    .setUploadPermissionsToShare(file, shareByLinkAllowEditing.isChecked());
+        }
+    }
+
+    @OnClick(R.id.overflow_menu_share_link)
+    public void showLinkOverflowMenu() {
+        Context context = getContext();
+        if (context != null && ThemeUtils.themingEnabled(context)) {
+            // use grey as fallback for elements where custom theming is not available
+            context.getTheme().applyStyle(R.style.FallbackThemingTheme, true);
+        } else {
+            context = getActivity();
+        }
+
+        PopupMenu popup = new PopupMenu(context, overflowMenuShareLink);
+        popup.inflate(R.menu.file_detail_sharing_link_menu);
+        prepareOptionsMenu(popup.getMenu());
+        popup.setOnMenuItemClickListener(this::optionsItemSelected);
+        popup.show();
+    }
+
+    private void prepareOptionsMenu(Menu menu) {
+        Resources res = getResources();
+        FileDetailSharingFragmentHelper.setupHideFileListingMenuItem(
+                menu.findItem(R.id.action_share_link_hide_file_listing),
+                file.isFolder(),
+                shareByLinkAllowEditing.isChecked(),
+                publicShare.getPermissions()
+        );
+        FileDetailSharingFragmentHelper.setupPasswordMenuItem(
+                menu.findItem(R.id.action_share_link_password),
+                publicShare.isPasswordProtected()
+        );
+        FileDetailSharingFragmentHelper.setupExpirationDateMenuItem(
+                menu.findItem(R.id.action_share_link_expiration_date),
+                publicShare.getExpirationDate(),
+                res
+        );
+    }
+
+    private boolean optionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case R.id.action_share_link_hide_file_listing: {
+                item.setChecked(!item.isChecked());
+                if (capabilities.getFilesFileDrop().isTrue()) {
+                    ((FileActivity) getActivity()).getFileOperationsHelper().
+                            setHideFileListingPermissionsToShare(publicShare, item.isChecked());
+                } else {
+                    // not supported in ownCloud
+                    Snackbar.make(getView(), R.string.files_drop_not_supported, Snackbar.LENGTH_LONG)
+                            .setAction(R.string.learn_more, v -> {
+                                Intent i = new Intent(Intent.ACTION_VIEW);
+                                i.setData(Uri.parse(getString(R.string.url_server_install)));
+                                startActivity(i);
+                            })
+                            .show();
+                }
+                return true;
+            }
+            case R.id.action_share_link_password: {
+                requestPasswordForShareViaLink(false);
+                return true;
+            }
+            case R.id.action_share_link_expiration_date: {
+                ExpirationDatePickerDialogFragment dialog = ExpirationDatePickerDialogFragment.newInstance(file, -1);
+                dialog.show(
+                        getActivity().getSupportFragmentManager(),
+                        ExpirationDatePickerDialogFragment.DATE_PICKER_DIALOG
+                );
+                return true;
+            }
+            default:
+                return super.onOptionsItemSelected(item);
+        }
+    }
+
     /**
-     * Fix scroll in listview when the parent is a ScrollView
+     * Updates the UI after the result of an update operation on the edited {@link OCFile}.
+     *
+     * @param result {@link RemoteOperationResult} of an update on the edited {@link OCFile} sharing information.
+     * @param file   the edited {@link OCFile}
      */
-    private static void setListViewHeightBasedOnChildren(ListView listView) {
-        ListAdapter listAdapter = listView.getAdapter();
-        if (listAdapter == null) {
-            return;
-        }
-        int desiredWidth = View.MeasureSpec.makeMeasureSpec(listView.getWidth(), View.MeasureSpec.AT_MOST);
-        int totalHeight = 0;
-        View view = null;
-        for (int i = 0; i < listAdapter.getCount(); i++) {
-            view = listAdapter.getView(i, view, listView);
-            if (i == 0) {
-                view.setLayoutParams(new ViewGroup.LayoutParams(desiredWidth, ViewGroup.LayoutParams.WRAP_CONTENT));
+    public void onUpdateShareInformation(RemoteOperationResult result, OCFile file) {
+        this.file = file;
+
+        if (result.isSuccess()) {
+            refreshUiFromDB();
+        } else {
+            setupView();
+        }
+    }
+
+    /**
+     * Get {@link OCShare} instance from DB and updates the UI.
+     *
+     * Depends on the parent Activity provides a {@link com.owncloud.android.datamodel.FileDataStorageManager}
+     * instance ready to use. If not ready, does nothing.
+     */
+    private void refreshUiFromDB() {
+        FileDataStorageManager storageManager = ((FileActivity) getActivity()).getStorageManager();
+        if (storageManager != null) {
+            if (publicShare != null) {
+                // Get edited shared by link
+                publicShare = storageManager.getShareById(publicShare.getId());
             }
-            view.measure(desiredWidth, View.MeasureSpec.UNSPECIFIED);
-            totalHeight += view.getMeasuredHeight();
+
+            // Updates UI with new state
+            setupView();
+        }
+    }
+
+    @Override
+    public void unshareWith(OCShare share) {
+        ((FileActivity) getActivity()).getFileOperationsHelper()
+                .unshareFileWithUserOrGroup(file, share.getShareType(), share.getShareWith());
+    }
+
+    @Override
+    public int updatePermissionsToShare(OCShare share, boolean canReshare, boolean canEdit,
+                                        boolean canEditCreate, boolean canEditChange,
+                                        boolean canEditDelete) {
+        SharePermissionsBuilder spb = new SharePermissionsBuilder();
+        spb.setSharePermission(canReshare);
+        if (file.isFolder()) {
+            spb.setUpdatePermission(canEditChange)
+                    .setCreatePermission(canEditCreate)
+                    .setDeletePermission(canEditDelete);
+        } else {
+            spb.setUpdatePermission(canEdit);
+        }
+        int permissions = spb.build();
+
+        ((FileActivity) getActivity()).getFileOperationsHelper().
+                setPermissionsToShare(
+                        share,
+                        permissions
+                )
+        ;
+
+        return permissions;
+    }
+
+    /**
+     * Starts a dialog that requests a password to the user to protect a share link.
+     *
+     * @param createShare When 'true', the request for password will be followed by the creation of a new
+     *                    public link; when 'false', a public share is assumed to exist, and the password
+     *                    is bound to it.
+     */
+    public void requestPasswordForShareViaLink(boolean createShare) {
+        SharePasswordDialogFragment dialog = SharePasswordDialogFragment.newInstance(file, createShare);
+        dialog.show(getChildFragmentManager(), SharePasswordDialogFragment.PASSWORD_FRAGMENT);
+    }
+
+    /**
+     * Get known server capabilities from DB
+     * 
+     * Depends on the parent Activity provides a {@link com.owncloud.android.datamodel.FileDataStorageManager}
+     * instance ready to use. If not ready, does nothing.
+     */
+    public void refreshCapabilitiesFromDB() {
+        if (((FileActivity) getActivity()).getStorageManager() != null) {
+            capabilities = ((FileActivity) getActivity()).getStorageManager().getCapability(account.name);
+        }
+    }
+
+    /**
+     * Get public link from the DB to fill in the "Share link" section in the UI.
+     * 
+     * Takes into account server capabilities before reading database.
+     * 
+     * Depends on the parent Activity provides a {@link com.owncloud.android.datamodel.FileDataStorageManager}
+     * instance ready to use. If not ready, does nothing.
+     */
+    public void refreshPublicShareFromDB() {
+        if (FileDetailSharingFragmentHelper.isPublicShareDisabled(capabilities)) {
+            shareByLinkContainer.setVisibility(View.GONE);
+        } else if (((FileActivity) getActivity()).getStorageManager() != null) {
+            // Get public share
+            publicShare = ((FileActivity) getActivity()).getStorageManager().getFirstShareByPathAndType(
+                    file.getRemotePath(),
+                    ShareType.PUBLIC_LINK,
+                    ""
+            );
+
+            // Update public share section
+            updatePublicShareSection();
+        }
+    }
+
+    /**
+     * Updates in the UI the section about public share with the information
+     * in the current public share bound to, if any.
+     */
+    private void updatePublicShareSection() {
+        if (publicShare != null && ShareType.PUBLIC_LINK.equals(publicShare.getShareType())) {
+            shareByLink.setChecked(true);
+
+            if (publicShare.getPermissions() > OCShare.READ_PERMISSION_FLAG) {
+                shareByLinkAllowEditing.setChecked(true);
+            } else {
+                shareByLinkAllowEditing.setChecked(false);
+            }
+
+            setShareByLinkInfo(true);
+        } else {
+            setShareByLinkInfo(false);
         }
-        ViewGroup.LayoutParams params = listView.getLayoutParams();
-        params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
-        listView.setLayoutParams(params);
-        listView.requestLayout();
     }
 }

+ 11 - 2
src/main/java/com/owncloud/android/ui/fragment/FileFragment.java

@@ -1,8 +1,10 @@
-/**
+/*
  *   ownCloud Android client application
  *
  *   @author David A. Velasco
+ *   @author Andy Scherzinger
  *   Copyright (C) 2015  ownCloud Inc.
+ *   Copyright (C) 2018 Andy Scherzinger
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -15,7 +17,6 @@
  *
  *   You should have received a copy of the GNU General Public License
  *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
  */
 
 package com.owncloud.android.ui.fragment;
@@ -131,6 +132,14 @@ public class FileFragment extends Fragment {
          */
         void showDetails(OCFile file);
 
+        /**
+         * Request the parent activity to show the details of an {@link OCFile}.
+         *
+         * @param file      File to show details
+         * @param activeTab the active tab
+         */
+        void showDetails(OCFile file, int activeTab);
+
         
         ///// TO UNIFY IN A SINGLE CALLBACK METHOD - EVENT NOTIFICATIONs  -> something happened
         // inside the fragment, MAYBE activity is interested --> unify in notification method

+ 7 - 1
src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java

@@ -4,8 +4,10 @@
  * @author Bartek Przybylski
  * @author masensio
  * @author David A. Velasco
+ * @author Andy Scherzinger
  * Copyright (C) 2011  Bartek Przybylski
  * Copyright (C) 2016 ownCloud Inc.
+ * Copyright (C) 2018 Andy Scherzinger
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2,
@@ -393,7 +395,11 @@ public class OCFileListFragment extends ExtendedListFragment implements
 
     @Override
     public void onShareIconClick(OCFile file) {
-        mContainerActivity.getFileOperationsHelper().sendShareFile(file);
+        if (file.isFolder()) {
+            mContainerActivity.showDetails(file, 1);
+        } else {
+            mContainerActivity.getFileOperationsHelper().sendShareFile(file);
+        }
     }
 
     @Override

+ 6 - 29
src/main/java/com/owncloud/android/ui/fragment/ShareFileFragment.java

@@ -4,17 +4,19 @@
  * @author masensio
  * @author David A. Velasco
  * @author Juan Carlos González Cabrero
+ * @author Andy Scherzinger
  * Copyright (C) 2015 ownCloud Inc.
- * <p/>
+ * Copyright (C) 2018 Andy Scherzinger
+ *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2,
  * as published by the Free Software Foundation.
- * <p/>
+ *
  * 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.
- * <p/>
+ *
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
@@ -40,7 +42,6 @@ import android.widget.Button;
 import android.widget.CompoundButton;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
-import android.widget.ListAdapter;
 import android.widget.ListView;
 import android.widget.ScrollView;
 import android.widget.TextView;
@@ -651,7 +652,6 @@ public class ShareFileFragment extends Fragment implements ShareUserListAdapter.
             noShares.setVisibility(View.GONE);
             usersList.setVisibility(View.VISIBLE);
             usersList.setAdapter(mUserGroupsAdapter);
-            setListViewHeightBasedOnChildren(usersList);
         } else {
             noShares.setVisibility(View.VISIBLE);
             usersList.setVisibility(View.GONE);
@@ -846,7 +846,7 @@ public class ShareFileFragment extends Fragment implements ShareUserListAdapter.
     }
 
 
-    /// BEWARE: next methods will failed with NullPointerException if called before onCreateView() finishes
+    // BEWARE: following methods will fail with NullPointerException if called before onCreateView() finishes
 
     private SwitchCompat getShareViaLinkSwitch() {
         return (SwitchCompat) getView().findViewById(R.id.shareViaLinkSectionSwitch);
@@ -908,29 +908,6 @@ public class ShareFileFragment extends Fragment implements ShareUserListAdapter.
         getHideFileListingPermissionSection().setVisibility(View.GONE);
     }
 
-    public static void setListViewHeightBasedOnChildren(ListView listView) {
-        ListAdapter listAdapter = listView.getAdapter();
-        if (listAdapter == null) {
-            return;
-        }
-        int desiredWidth = View.MeasureSpec.makeMeasureSpec(listView.getWidth(), View.MeasureSpec.AT_MOST);
-        int totalHeight = 0;
-        View view = null;
-        for (int i = 0; i < listAdapter.getCount(); i++) {
-            view = listAdapter.getView(i, view, listView);
-            if (i == 0) {
-                view.setLayoutParams(new ViewGroup.LayoutParams(desiredWidth, ViewGroup.LayoutParams.WRAP_CONTENT));
-            }
-            view.measure(desiredWidth, View.MeasureSpec.UNSPECIFIED);
-            totalHeight += view.getMeasuredHeight();
-        }
-        ViewGroup.LayoutParams params = listView.getLayoutParams();
-        params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
-        listView.setLayoutParams(params);
-        listView.requestLayout();
-    }
-
-
     /**
      * Starts a dialog that requests a password to the user to protect a share link.
      *

+ 136 - 0
src/main/java/com/owncloud/android/ui/fragment/util/FileDetailSharingFragmentHelper.java

@@ -0,0 +1,136 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Andy Scherzinger
+ * Copyright (C) 2018 Andy Scherzinger
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.owncloud.android.ui.fragment.util;
+
+import android.app.SearchManager;
+import android.content.ComponentName;
+import android.content.res.Resources;
+import android.support.v7.widget.SearchView;
+import android.view.MenuItem;
+import android.view.inputmethod.EditorInfo;
+
+import com.owncloud.android.R;
+import com.owncloud.android.lib.resources.shares.OCShare;
+import com.owncloud.android.lib.resources.status.OCCapability;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * Helper calls for visibility logic of the sharing fragment.
+ */
+public class FileDetailSharingFragmentHelper {
+
+    /**
+     * Sets checked/visiblity state on the given {@link MenuItem} based on the given criteria.
+     *
+     * @param fileListing            the {@link MenuItem} to be setup
+     * @param isFolder               flag if it is a folder
+     * @param isEditingAllowed       flag if editing is allowed
+     * @param publicSharePermissions share permissions of the link
+     */
+    public static void setupHideFileListingMenuItem(MenuItem fileListing,
+                                                    boolean isFolder,
+                                                    boolean isEditingAllowed,
+                                                    int publicSharePermissions) {
+        if (!isFolder) {
+            fileListing.setVisible(false);
+        } else {
+            if (isEditingAllowed) {
+                boolean readOnly = (publicSharePermissions & OCShare.READ_PERMISSION_FLAG) != 0;
+                fileListing.setChecked(!readOnly);
+            } else {
+                fileListing.setVisible(false);
+            }
+        }
+    }
+
+    /**
+     * sets up the password {@link MenuItem}'s title based on the fact if a password is present.
+     *
+     * @param password            the password {@link MenuItem}
+     * @param isPasswordProtected flag is a password is present
+     */
+    public static void setupPasswordMenuItem(MenuItem password, boolean isPasswordProtected) {
+        if (isPasswordProtected) {
+            password.setTitle(R.string.share_password_title);
+        } else {
+            password.setTitle(R.string.share_no_password_title);
+        }
+    }
+
+    /**
+     * sets up the expiration date {@link MenuItem}'s title based on the fact if an expiration date is present.
+     *
+     * @param expirationDate      the expiration date {@link MenuItem}
+     * @param expirationDateValue the expiration date
+     * @param res                 Resources to load the corresponding strings.
+     */
+    public static void setupExpirationDateMenuItem(MenuItem expirationDate, long expirationDateValue, Resources res) {
+        if (expirationDateValue > 0) {
+            expirationDate.setTitle(res.getString(
+                    R.string.share_expiration_date_label,
+                    SimpleDateFormat.getDateInstance().format(new Date(expirationDateValue))
+            ));
+        } else {
+            expirationDate.setTitle(R.string.share_no_expiration_date_label);
+        }
+    }
+
+    /**
+     * sets up the {@link SearchView}.
+     *
+     * @param searchManager the {@link SearchManager}
+     * @param searchView    the {@link SearchView}
+     * @param componentName the {@link ComponentName}
+     */
+    public static void setupSearchView(SearchManager searchManager, SearchView searchView, ComponentName componentName) {
+        // assumes parent activity is the searchable activity
+        searchView.setSearchableInfo(searchManager.getSearchableInfo(componentName));
+
+        // do not iconify the widget; expand it by default
+        searchView.setIconifiedByDefault(false);
+
+        // avoid fullscreen with softkeyboard
+        searchView.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
+
+        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
+            @Override
+            public boolean onQueryTextSubmit(String query) {
+                // return true to prevent the query from being processed;
+                return true;
+            }
+
+            @Override
+            public boolean onQueryTextChange(String newText) {
+                // leave it for the parent listener in the hierarchy / default behaviour
+                return false;
+            }
+        });
+    }
+
+    /**
+     * @return 'True' when public share is disabled in the server.
+     */
+    public static boolean isPublicShareDisabled(OCCapability capabilities) {
+        return (capabilities != null && capabilities.getFilesSharingPublicEnabled().isFalse());
+    }
+}

+ 32 - 4
src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java

@@ -4,7 +4,9 @@
  * @author masensio
  * @author David A. Velasco
  * @author Juan Carlos González Cabrero
+ * @author Andy Scherzinger
  * Copyright (C) 2015 ownCloud Inc.
+ * Copyright (C) 2018 Andy Scherzinger
  * 
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2,
@@ -83,7 +85,7 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 /**
- *
+ * Helper implementation for file operations locally and remote.
  */
 public class FileOperationsHelper {
 
@@ -537,6 +539,29 @@ public class FileOperationsHelper {
         queueShareIntent(updateShareIntent);
     }
 
+    /**
+     * Updates a public share on a file to set its expiration date.
+     * Starts a request to do it in {@link OperationsService}
+     *
+     * @param share                  {@link OCShare} instance which permissions will be updated.
+     * @param expirationTimeInMillis Expiration date to set. A negative value clears the current expiration
+     *                               date, leaving the link unrestricted. Zero makes no change.
+     */
+    public void setExpirationDateToShare(OCShare share, long expirationTimeInMillis) {
+        Intent updateShareIntent = new Intent(mFileActivity, OperationsService.class);
+        updateShareIntent.setAction(OperationsService.ACTION_UPDATE_SHARE);
+        updateShareIntent.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
+        updateShareIntent.putExtra(OperationsService.EXTRA_SHARE_ID, share.getId());
+        updateShareIntent.putExtra(
+                OperationsService.EXTRA_SHARE_EXPIRATION_DATE_IN_MILLIS,
+                expirationTimeInMillis
+        );
+        updateShareIntent.putExtra(
+                OperationsService.EXTRA_SHARE_PERMISSIONS,
+                0
+        );
+        queueShareIntent(updateShareIntent);
+    }
 
     /**
      * Updates a share on a file to set its access permissions.
@@ -615,17 +640,21 @@ public class FileOperationsHelper {
         return false;
     }
 
-    public void sendShareFile(OCFile file) {
+    public void sendShareFile(OCFile file, boolean hideNcSharingOptions) {
         // Show dialog
         FragmentManager fm = mFileActivity.getSupportFragmentManager();
         FragmentTransaction ft = fm.beginTransaction();
         ft.addToBackStack(null);
 
-        SendShareDialog mSendShareDialog = SendShareDialog.newInstance(file);
+        SendShareDialog mSendShareDialog = SendShareDialog.newInstance(file, hideNcSharingOptions);
         mSendShareDialog.setFileOperationsHelper(this);
         mSendShareDialog.show(ft, "TAG_SEND_SHARE_DIALOG");
     }
 
+    public void sendShareFile(OCFile file) {
+        sendShareFile(file, false);
+    }
+
     public void syncFiles(Collection<OCFile> files) {
         for (OCFile file : files) {
             syncFile(file);
@@ -709,7 +738,6 @@ public class FileOperationsHelper {
             intent.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
             intent.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
             mFileActivity.startService(intent);
-
         }
     }
 

+ 5 - 0
src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java

@@ -312,6 +312,11 @@ public class PreviewImageActivity extends FileActivity implements
         finish();
     }
 
+    @Override
+    public void showDetails(OCFile file, int activeTab) {
+        showDetails(file);
+    }
+
     public void requestForDownload(OCFile file) {
         if (mDownloaderBinder == null) {
             Log_OC.d(TAG, "requestForDownload called without binder to download service");

+ 5 - 1
src/main/java/com/owncloud/android/utils/BitmapUtils.java

@@ -270,7 +270,7 @@ public class BitmapUtils {
      * @param name The name
      * @return corresponding RGB color
      * @throws UnsupportedEncodingException if the charset is not supported
-     * @throws NoSuchAlgorithmException if the specified algorithm is not available
+     * @throws NoSuchAlgorithmException     if the specified algorithm is not available
      */
     public static int[] calculateHSL(String name) throws UnsupportedEncodingException, NoSuchAlgorithmException {
         // using adapted algorithm from https://github.com/nextcloud/server/blob/master/core/js/placeholder.js#L126
@@ -375,6 +375,10 @@ public class BitmapUtils {
      * @return the circular bitmap
      */
     public static RoundedBitmapDrawable bitmapToCircularBitmapDrawable(Resources resources, Bitmap bitmap) {
+        if (bitmap == null) {
+            return null;
+        }
+        
         RoundedBitmapDrawable roundedBitmap = RoundedBitmapDrawableFactory.create(resources, bitmap);
         roundedBitmap.setCircular(true);
         return roundedBitmap;

+ 55 - 32
src/main/java/com/owncloud/android/utils/DisplayUtils.java

@@ -25,6 +25,7 @@
 package com.owncloud.android.utils;
 
 import android.accounts.Account;
+import android.accounts.AccountManager;
 import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
 import android.app.Activity;
@@ -433,50 +434,72 @@ public class DisplayUtils {
     }
 
     /**
-     * fetches and sets the avatar of the current account in the drawer in case the drawer is available.
+     * fetches and sets the avatar of the given account in the passed callContext
      *
-     * @param account        the account to be set in the drawer
+     * @param account        the account to be used to connect to server
      * @param avatarRadius   the avatar radius
      * @param resources      reference for density information
      * @param storageManager reference for caching purposes
+     * @param callContext    which context is called to set the generated avatar
      */
-    public static void setAvatar(Account account, AvatarGenerationListener listener, float avatarRadius,
-                                 Resources resources, FileDataStorageManager storageManager, Object callContext) {
-        if (account != null) {
-            if (callContext instanceof View) {
-                ((View) callContext).setContentDescription(account.name);
-            }
+    public static void setAvatar(@NonNull Account account, AvatarGenerationListener listener,
+                                 float avatarRadius, Resources resources, FileDataStorageManager storageManager,
+                                 Object callContext, Context context) {
+
+        AccountManager accountManager = AccountManager.get(context);
+        String userId = accountManager.getUserData(account,
+                com.owncloud.android.lib.common.accounts.AccountUtils.Constants.KEY_USER_ID);
+
+        setAvatar(account, userId, listener, avatarRadius, resources, storageManager, callContext, context);
+    }
+
+    /**
+     * fetches and sets the avatar of the given account in the passed callContext
+     *
+     * @param account        the account to be used to connect to server
+     * @param userId         the userId which avatar should be set
+     * @param avatarRadius   the avatar radius
+     * @param resources      reference for density information
+     * @param storageManager reference for caching purposes
+     * @param callContext    which context is called to set the generated avatar
+     */
+    public static void setAvatar(@NonNull Account account, @NonNull String userId, AvatarGenerationListener listener,
+                                 float avatarRadius, Resources resources, FileDataStorageManager storageManager,
+                                 Object callContext, Context context) {
+        if (callContext instanceof View) {
+            ((View) callContext).setContentDescription(account.name);
+        }
 
-            ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(
-                    MainApp.getAppContext().getContentResolver());
+        ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(context.getContentResolver());
 
-            String eTag = arbitraryDataProvider.getValue(account, ThumbnailsCacheManager.AVATAR);
+        String serverName = account.name.substring(account.name.lastIndexOf('@') + 1, account.name.length());
+        String eTag = arbitraryDataProvider.getValue(userId + "@" + serverName, ThumbnailsCacheManager.AVATAR);
+        String avatarKey = "a_" + userId + "_" + serverName + "_" + eTag;
 
-            // first show old one
-            Drawable avatar = BitmapUtils.bitmapToCircularBitmapDrawable(resources,
-                    ThumbnailsCacheManager.getBitmapFromDiskCache("a_" + account.name + "_" + eTag));
+        // first show old one
+        Drawable avatar = BitmapUtils.bitmapToCircularBitmapDrawable(resources,
+                ThumbnailsCacheManager.getBitmapFromDiskCache(avatarKey));
 
-            // if no one exists, show colored icon with initial char
-            if (avatar == null) {
-                try {
-                    avatar = TextDrawable.createAvatar(account.name, avatarRadius);
-                } catch (Exception e) {
-                    Log_OC.e(TAG, "Error calculating RGB value for active account icon.", e);
-                    avatar = resources.getDrawable(R.drawable.account_circle_white);
-                }
+        // if no one exists, show colored icon with initial char
+        if (avatar == null) {
+            try {
+                avatar = TextDrawable.createAvatarByUserId(userId, avatarRadius);
+            } catch (Exception e) {
+                Log_OC.e(TAG, "Error calculating RGB value for active account icon.", e);
+                avatar = resources.getDrawable(R.drawable.account_circle_white);
             }
+        }
 
-            // check for new avatar, eTag is compared, so only new one is downloaded
-            if (ThumbnailsCacheManager.cancelPotentialAvatarWork(account.name, callContext)) {
-                final ThumbnailsCacheManager.AvatarGenerationTask task =
-                        new ThumbnailsCacheManager.AvatarGenerationTask(listener, callContext, storageManager,
-                                account, resources, avatarRadius);
+        // check for new avatar, eTag is compared, so only new one is downloaded
+        if (ThumbnailsCacheManager.cancelPotentialAvatarWork(userId, callContext)) {
+            final ThumbnailsCacheManager.AvatarGenerationTask task =
+                    new ThumbnailsCacheManager.AvatarGenerationTask(listener, callContext, storageManager,
+                            account, resources, avatarRadius, userId, serverName, context);
 
-                final ThumbnailsCacheManager.AsyncAvatarDrawable asyncDrawable =
-                        new ThumbnailsCacheManager.AsyncAvatarDrawable(resources, avatar, task);
-                listener.avatarGenerated(asyncDrawable, callContext);
-                task.execute(account.name);
-            }
+            final ThumbnailsCacheManager.AsyncAvatarDrawable asyncDrawable =
+                    new ThumbnailsCacheManager.AsyncAvatarDrawable(resources, avatar, task);
+            listener.avatarGenerated(asyncDrawable, callContext);
+            task.execute(userId);
         }
     }
 

+ 9 - 2
src/main/java/com/owncloud/android/utils/ThemeUtils.java

@@ -4,8 +4,8 @@
  * @author Tobias Kaminsky
  * @author Andy Scherzinger
  * Copyright (C) 2017 Tobias Kaminsky
- * Copyright (C) 2017 Andy Scherzinger
- * Copyright (C) 2017 Nextcloud GmbH.
+ * Copyright (C) 2017 Nextcloud GmbH
+ * Copyright (C) 2018 Andy Scherzinger
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as published by
@@ -23,6 +23,7 @@
 package com.owncloud.android.utils;
 
 import android.accounts.Account;
+import android.app.Activity;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.graphics.Color;
@@ -212,6 +213,12 @@ public class ThemeUtils {
         }
     }
 
+    public static void setStatusBarColor(Activity activity, @ColorInt int color) {
+        if (activity != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            activity.getWindow().setStatusBarColor(color);
+        }
+    }
+
     /**
      * Adjust lightness of given color
      *

+ 31 - 0
src/main/res/drawable/divider.xml

@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Nextcloud Android client application
+
+  Copyright (C) 2018 Andy Scherzinger
+
+  This program is free software; you can redistribute it and/or
+  modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+  License as published by the Free Software Foundation; either
+  version 3 of the License, or 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+
+  You should have received a copy of the GNU Affero General Public
+  License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:bottom="0dp"
+        android:left="72dp"
+        android:right="0dp"
+        android:top="0dp">
+        <shape android:shape="rectangle">
+            <solid android:color="@color/list_divider_background" />
+            <size android:height="1dp" />
+        </shape>
+    </item>
+</layer-list>

+ 23 - 0
src/main/res/drawable/ic_alert_octagon.xml

@@ -0,0 +1,23 @@
+<!--
+    @author Google LLC
+    Copyright (C) 2018 Google LLC
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:height="24dp"
+    android:width="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path android:fillColor="#757575" android:pathData="M13,13H11V7H13M12,17.3A1.3,1.3 0 0,1 10.7,16A1.3,1.3 0 0,1 12,14.7A1.3,1.3 0 0,1 13.3,16A1.3,1.3 0 0,1 12,17.3M15.73,3H8.27L3,8.27V15.73L8.27,21H15.73L21,15.73V8.27L15.73,3Z" />
+</vector>

+ 0 - 32
src/main/res/layout/file_details_empty.xml

@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 
-  ownCloud Android client application
-
-  Copyright (C) 2012  Bartek Przybylski
-  Copyright (C) 2015 ownCloud Inc.
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU General Public License version 2,
-  as published by the Free Software Foundation.
-
-  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 <http://www.gnu.org/licenses/>.
- -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-	android:layout_width="match_parent"
-	android:layout_height="match_parent"
-    android:orientation="vertical">
-
-    <TextView 
-        android:layout_width="fill_parent"
-        android:layout_height="fill_parent"
-        android:gravity="center_vertical|center_horizontal"
-        android:text="@string/filedetails_select_file"
-        />
-
-</LinearLayout>

+ 15 - 26
src/main/res/layout/file_details_fragment.xml

@@ -4,6 +4,7 @@
 
   Copyright (C) 2012 Bartek Przybylski
   Copyright (C) 2017 ownCloud GmbH.
+  Copyright (C) 2018 Andy Scherzinger
 
   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License version 2,
@@ -19,7 +20,7 @@
 -->
 <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/fdScrollView"
+    android:id="@+id/scrollView"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:fillViewport="true">
@@ -29,27 +30,13 @@
         android:layout_height="wrap_content"
         android:orientation="vertical">
 
-        <ImageView
-            android:id="@+id/fdIcon"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_margin="@dimen/standard_margin"
-            android:contentDescription="@string/file_icon"
-            android:src="@drawable/file" />
-
-        <View
-            android:id="@+id/fdIcon_divider"
-            android:layout_width="match_parent"
-            android:layout_height="1dp"
-            android:background="@color/list_divider_background" />
-
         <LinearLayout
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:paddingTop="@dimen/standard_padding"
             android:paddingLeft="@dimen/standard_padding"
             android:paddingRight="@dimen/standard_padding"
-            android:paddingBottom="@dimen/standard_half_padding"
+            android:paddingBottom="@dimen/standard_padding"
             android:orientation="horizontal">
 
             <LinearLayout
@@ -59,23 +46,24 @@
                 android:orientation="vertical">
 
                 <TextView
-                    android:id="@+id/fdFilename"
+                    android:id="@+id/filename"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:ellipsize="middle"
                     android:text="@string/placeholder_filename"
                     android:textColor="@color/black"
-                    android:textSize="16sp" />
+                    android:textStyle="bold"
+                    android:textSize="20sp" />
 
                 <LinearLayout
-                    android:id="@+id/fdFiledetails"
+                    android:id="@+id/filedetails"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:gravity="center"
                     android:orientation="horizontal">
 
                     <ImageView
-                        android:id="@+id/fdFavorite"
+                        android:id="@+id/favorite"
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
                         android:layout_marginEnd="@dimen/standard_eighth_margin"
@@ -86,7 +74,7 @@
                         android:src="@drawable/ic_star_outline" />
 
                     <TextView
-                        android:id="@+id/fdSize"
+                        android:id="@+id/size"
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
                         android:text="@string/placeholder_fileSize"
@@ -106,7 +94,7 @@
                         android:textSize="@dimen/two_line_secondary_text_size" />
 
                     <TextView
-                        android:id="@+id/fdModified"
+                        android:id="@+id/modified"
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
                         android:gravity="end"
@@ -144,6 +132,7 @@
         </LinearLayout>
 
         <LinearLayout
+            android:id="@+id/progressBlock"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:paddingLeft="@dimen/standard_padding"
@@ -152,13 +141,12 @@
             android:orientation="vertical">
 
             <TextView
-                android:id="@+id/fdProgressText"
+                android:id="@+id/progressText"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:text="@string/downloader_download_in_progress_ticker" />
 
             <LinearLayout
-                android:id="@+id/fdProgressBlock"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:layout_marginBottom="@dimen/standard_quarter_margin"
@@ -166,7 +154,7 @@
                 android:orientation="horizontal">
 
                 <ProgressBar
-                    android:id="@+id/fdProgressBar"
+                    android:id="@+id/progressBar"
                     style="?android:attr/progressBarStyleHorizontal"
                     android:layout_width="0dp"
                     android:layout_height="wrap_content"
@@ -175,7 +163,7 @@
                     android:indeterminateOnly="false" />
 
                 <ImageButton
-                    android:id="@+id/fdCancelBtn"
+                    android:id="@+id/cancelBtn"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:layout_marginLeft="@dimen/standard_half_margin"
@@ -199,6 +187,7 @@
             android:layout_height="wrap_content"
             app:tabGravity="center"
             app:tabMode="fixed"
+            app:tabTextAppearance="@style/AppTabTextAppearance"
             android:background="@color/white" />
 
         <android.support.v4.view.ViewPager

+ 41 - 19
src/main/res/layout/file_details_share_user_item.xml

@@ -1,7 +1,9 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
   ownCloud Android client application
+
   Copyright (C) 2015 ownCloud Inc.
+  Copyright (C) 2018 Andy Scherzinger
 
   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License version 2,
@@ -21,34 +23,54 @@
     android:layout_height="wrap_content"
     android:orientation="horizontal"
     android:weightSum="1"
-    android:paddingTop="@dimen/standard_eigth_padding"
-    android:paddingBottom="@dimen/standard_eigth_padding"
     tools:ignore="UseCompoundDrawables">
 
         <ImageView
-            android:id="@+id/userIcon"
-            android:layout_width="@dimen/file_details_user_icon_layout_width"
-            android:layout_height="@dimen/file_details_user_icon_layout_height"
-            android:src="@drawable/ic_user"
+            android:id="@+id/avatar"
+            android:layout_width="@dimen/user_icon_size"
+            android:layout_height="@dimen/user_icon_size"
             android:layout_gravity="center_vertical"
-            android:layout_marginTop="@dimen/standard_half_margin"
             android:layout_marginBottom="@dimen/standard_half_margin"
-            android:layout_marginLeft="@dimen/standard_margin"
-            android:layout_marginStart="@dimen/standard_margin"
             android:layout_marginEnd="@dimen/standard_margin"
+            android:layout_marginLeft="@dimen/standard_margin"
             android:layout_marginRight="@dimen/standard_margin"
-            android:contentDescription="@string/user_icon"/>
+            android:layout_marginStart="@dimen/standard_margin"
+            android:layout_marginTop="@dimen/standard_half_margin"
+            android:contentDescription="@string/user_icon"
+            android:src="@drawable/ic_user" />
 
         <TextView
-            android:layout_width="match_parent"
+            android:id="@+id/name"
+            android:layout_width="0dp"
             android:layout_height="match_parent"
-            android:textSize="@dimen/file_details_username_text_size"
-            android:text="@string/username"
-            android:id="@+id/userOrGroupName"
-            android:layout_marginRight="@dimen/standard_margin"
-            android:layout_marginEnd="@dimen/standard_margin"
-            android:singleLine="true"
-            android:ellipsize="middle"
+            android:layout_marginEnd="@dimen/standard_quarter_margin"
+            android:layout_marginRight="@dimen/standard_quarter_margin"
+            android:layout_weight="1"
+            android:ellipsize="end"
             android:gravity="center_vertical"
-            android:textColor="@color/black"/>
+            android:singleLine="true"
+            android:text="@string/username"
+            android:textColor="@color/black"
+            android:textSize="@dimen/file_details_username_text_size" />
+
+        <android.support.v7.widget.AppCompatCheckBox
+            android:id="@+id/allowEditing"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:textSize="16sp"
+            android:text="@string/edit_permission_label" />
+
+        <ImageView
+            android:id="@+id/editShareButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:layout_marginLeft="@dimen/standard_half_margin"
+            android:layout_marginStart="@dimen/standard_half_margin"
+            android:layout_marginEnd="@dimen/standard_half_margin"
+            android:layout_marginRight="@dimen/standard_half_margin"
+            android:contentDescription="@string/overflow_menu"
+            android:padding="@dimen/standard_half_padding"
+            android:src="@drawable/ic_dots_vertical" />
 </LinearLayout>

+ 57 - 46
src/main/res/layout/file_details_sharing_fragment.xml

@@ -18,85 +18,96 @@
   License along with this program.  If not, see <http://www.gnu.org/licenses/>.
 -->
 <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:layout_weight="1">
 
     <LinearLayout
-        android:id="@+id/fdShareContainer"
+        android:id="@+id/shareContainer"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:orientation="vertical"
-        android:paddingTop="@dimen/standard_padding">
+        android:paddingTop="@dimen/standard_eigth_padding">
 
-        <TextView
-            android:id="@+id/fdShareTitle"
-            style="@style/TextAppearance.AppCompat.Body2"
+        <android.support.v7.widget.SearchView
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_marginBottom="@dimen/standard_half_margin"
-            android:paddingLeft="@dimen/standard_padding"
-            android:paddingRight="@dimen/standard_padding"
-            android:text="@string/action_send_share"
-            android:textColor="@color/color_accent" />
+            android:id="@+id/searchView"
+            android:hint="@string/share_search"
+            android:layout_marginStart="@dimen/standard_eighth_margin"
+            android:layout_marginLeft="@dimen/standard_eighth_margin"
+            android:layout_marginEnd="@dimen/standard_margin"
+            android:layout_marginRight="@dimen/standard_margin"
+            style="@style/ownCloud.SearchView"/>
 
         <LinearLayout
+            android:id="@+id/share_by_link_container"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:layout_marginBottom="@dimen/standard_half_margin"
             android:orientation="horizontal"
             android:paddingLeft="@dimen/standard_padding"
             android:paddingRight="@dimen/standard_padding"
-            tools:ignore="UseCompoundDrawables">
+            android:paddingTop="@dimen/standard_padding">
 
-            <ImageView
-                android:id="@+id/fdShareLinkIcon"
-                android:layout_width="@dimen/shared_list_icon_width"
-                android:layout_height="@dimen/shared_list_icon_height"
-                android:layout_gravity="center_vertical"
-                android:layout_marginEnd="@dimen/standard_half_margin"
-                android:layout_marginRight="@dimen/standard_half_margin"
-                android:contentDescription="@string/shared_icon"
-                android:src="@drawable/shared_via_link"
-                android:visibility="gone" />
+            <android.support.v7.widget.AppCompatCheckBox
+                android:id="@+id/share_by_link"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"/>
 
             <TextView
-                android:id="@+id/fdSharebyLink"
-                android:layout_width="match_parent"
+                android:id="@+id/share_link_label"
+                android:layout_width="0dp"
                 android:layout_height="wrap_content"
-                android:layout_gravity="start"
-                android:text="@string/filedetails_share_link_disable" />
-        </LinearLayout>
+                android:layout_gravity="start|center"
+                android:textColor="@color/black"
+                android:layout_weight="1"
+                android:textSize="16sp"
+                android:paddingTop="@dimen/standard_half_padding"
+                android:paddingBottom="@dimen/standard_half_padding"
+                android:text="@string/share_via_link_section_title" />
 
-        <TextView
-            android:id="@+id/fdShareWithUsersTitle"
-            style="@style/TextAppearance.AppCompat.Body2"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_gravity="start"
-            android:layout_marginBottom="@dimen/standard_half_margin"
-            android:layout_marginTop="@dimen/standard_half_margin"
-            android:paddingLeft="@dimen/standard_padding"
-            android:paddingRight="@dimen/standard_padding"
-            android:text="@string/filedetails_share_users_with_access"
-            android:textColor="@color/color_accent" />
+            <android.support.v7.widget.AppCompatCheckBox
+                android:id="@+id/share_by_link_allow_editing"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:textSize="16sp"
+                android:text="@string/edit_permission_label"/>
+
+            <ImageView
+                android:id="@+id/overflow_menu_share_link"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="end|center"
+                android:layout_marginLeft="@dimen/standard_half_margin"
+                android:layout_marginStart="@dimen/standard_half_margin"
+                android:contentDescription="@string/overflow_menu"
+                android:paddingEnd="@dimen/zero"
+                android:paddingLeft="@dimen/standard_half_padding"
+                android:paddingRight="@dimen/zero"
+                android:paddingStart="@dimen/standard_half_padding"
+                android:src="@drawable/ic_dots_vertical" />
+        </LinearLayout>
 
-        <ListView
-            android:id="@+id/fdshareUsersList"
+        <android.support.v7.widget.RecyclerView
+            android:id="@+id/shareUsersList"
             android:layout_width="match_parent"
-            android:layout_height="0dip"
+            android:layout_height="0dp"
             android:layout_weight="1"
-            android:visibility="gone" />
+            android:divider="@drawable/divider"
+            android:dividerHeight="1dp"/>
 
         <TextView
-            android:id="@+id/fdShareNoUsers"
+            android:id="@+id/shareNoUsers"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:paddingBottom="@dimen/standard_padding"
+            android:paddingTop="@dimen/standard_half_padding"
             android:paddingLeft="@dimen/standard_padding"
             android:paddingRight="@dimen/standard_padding"
             android:text="@string/share_no_users"
-            android:textSize="@dimen/share_no_users_text_size" />
+            android:textSize="16sp" />
 
     </LinearLayout>
 

+ 2 - 2
src/main/res/layout/share_file_layout.xml

@@ -169,7 +169,7 @@
                     android:id="@+id/shareViaLinkEditPermissionSection"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    android:visibility="invisible"
+                    android:visibility="gone"
                     android:layout_marginBottom="@dimen/standard_half_margin"
                     >
 
@@ -203,8 +203,8 @@
                     android:id="@+id/shareViaLinkHideFileListingPermissionSection"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
+                    android:visibility="gone"
                     android:layout_marginBottom="@dimen/standard_half_margin"
-                    android:visibility="invisible"
                     >
 
                     <android.support.v7.widget.SwitchCompat

+ 1 - 0
src/main/res/layout/toolbar_standard.xml

@@ -34,6 +34,7 @@
             android:id="@+id/preview_image"
             android:layout_width="match_parent"
             android:layout_height="@dimen/nav_drawer_header_height"
+            android:contentDescription="@string/preview_image_description"
             android:scaleType="centerCrop"
             android:visibility="gone" />
 

+ 42 - 0
src/main/res/menu/file_detail_sharing_link_menu.xml

@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Nesxtcloud Android client application
+
+  Copyright (C) 2018 Andy Scherzinger
+
+  This program is free software; you can redistribute it and/or
+  modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+  License as published by the Free Software Foundation; either
+  version 3 of the License, or 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+
+  You should have received a copy of the GNU Affero General Public
+  License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:ignore="AppCompatResource">
+
+    <item
+        android:id="@+id/action_share_link_hide_file_listing"
+        android:showAsAction="never"
+        android:title="@string/share_via_link_hide_file_listing_permission_label"
+        android:checkable="true"
+        app:showAsAction="never" />
+    <item
+        android:id="@+id/action_share_link_password"
+        android:showAsAction="never"
+        android:title="@string/share_via_link_menu_password_label"
+        app:showAsAction="never" />
+    <item
+        android:id="@+id/action_share_link_expiration_date"
+        android:showAsAction="never"
+        android:title="@string/share_expiration_date_label"
+        app:showAsAction="never" />
+
+</menu>

+ 60 - 0
src/main/res/menu/file_detail_sharing_menu.xml

@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Nesxtcloud Android client application
+
+  Copyright (C) 2018 Andy Scherzinger
+
+  This program is free software; you can redistribute it and/or
+  modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+  License as published by the Free Software Foundation; either
+  version 3 of the License, or 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+
+  You should have received a copy of the GNU Affero General Public
+  License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:ignore="AppCompatResource">
+
+    <item
+        android:id="@+id/action_can_edit_create"
+        android:checkable="true"
+        android:showAsAction="never"
+        android:title="@string/share_privilege_can_edit_create"
+        app:showAsAction="never" />
+    <item
+        android:id="@+id/action_can_edit_change"
+        android:checkable="true"
+        android:showAsAction="never"
+        android:title="@string/share_privilege_can_edit_change"
+        app:showAsAction="never" />
+    <item
+        android:id="@+id/action_can_edit_delete"
+        android:checkable="true"
+        android:showAsAction="never"
+        android:title="@string/share_privilege_can_edit_delete"
+        app:showAsAction="never" />
+    <item
+        android:id="@+id/action_can_reshare"
+        android:checkable="true"
+        android:showAsAction="never"
+        android:title="@string/share_privilege_can_share"
+        app:showAsAction="never" />
+    <item
+        android:id="@+id/action_expiration_date"
+        android:showAsAction="never"
+        android:title="@string/share_expiration_date_label"
+        app:showAsAction="never" />
+    <item
+        android:id="@+id/action_unshare"
+        android:showAsAction="never"
+        android:title="@string/share_privilege_unshare"
+        app:showAsAction="never" />
+
+</menu>

+ 87 - 0
src/main/res/menu/file_details_actions_menu.xml

@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Nextcloud Android client application
+
+  Copyright (C) 2018 Andy Scherzinger
+
+  This program is free software; you can redistribute it and/or
+  modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+  License as published by the Free Software Foundation; either
+  version 3 of the License, or 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+
+  You should have received a copy of the GNU Affero General Public
+  License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+      xmlns:app="http://schemas.android.com/apk/res-auto"
+      xmlns:tools="http://schemas.android.com/tools"
+      tools:ignore="AppCompatResource">
+
+    <item
+        android:id="@+id/action_rename_file"
+        android:title="@string/common_rename"
+        app:showAsAction="never"
+        android:showAsAction="never" />
+    <item
+        android:id="@+id/action_download_file"
+        android:title="@string/filedetails_download"
+        app:showAsAction="never"
+        android:showAsAction="never" />
+    <item
+        android:id="@+id/action_send_file"
+        android:title="@string/common_send"
+        app:showAsAction="never"
+        android:showAsAction="never"
+        android:icon="@drawable/ic_share" />
+    <item
+        android:id="@+id/action_open_file_with"
+        android:title="@string/actionbar_open_with"
+        app:showAsAction="never"
+        android:showAsAction="never" />
+    <item
+        android:id="@+id/action_sync_file"
+        android:title="@string/filedetails_sync_file"
+        app:showAsAction="never"
+        android:showAsAction="never" />
+    <item
+        android:id="@+id/action_cancel_sync"
+        android:title="@string/common_cancel_sync"
+        app:showAsAction="never"
+        android:showAsAction="never" />
+    <item
+        android:id="@+id/action_keep_files_offline"
+        android:title="@string/favorite"
+        app:showAsAction="never"
+        android:showAsAction="never" />
+    <item
+        android:id="@+id/action_unset_keep_files_offline"
+        android:title="@string/unfavorite"
+        app:showAsAction="never"
+        android:showAsAction="never" />
+    <item
+        android:id="@+id/action_encrypted"
+        android:title="@string/encrypted"
+        app:showAsAction="never"
+        android:showAsAction="never" />
+    <item
+        android:id="@+id/action_unset_encrypted"
+        android:title="@string/unset_encrypted"
+        app:showAsAction="never"
+        android:showAsAction="never" />
+    <item
+        android:id="@+id/action_set_as_wallpaper"
+        android:title="@string/set_picture_as"
+        app:showAsAction="never"
+        android:showAsAction="never" />
+    <item
+        android:id="@+id/action_remove_file"
+        android:title="@string/common_remove"
+        app:showAsAction="never"
+        android:showAsAction="never" />
+
+</menu>

+ 1 - 3
src/main/res/values/dims.xml

@@ -53,6 +53,7 @@
     <dimen name="zero">0dp</dimen>
     <dimen name="account_item_layout_height">72dp</dimen>
     <dimen name="user_icon_size">40dp</dimen>
+    <dimen name="user_icon_radius">20dp</dimen>
     <dimen name="share_icon_size">56dp</dimen>
     <dimen name="alternate_margin">10dp</dimen>
     <dimen name="alternate_half_margin">5dp</dimen>
@@ -101,9 +102,6 @@
     <dimen name="empty_list_icon_layout_height">72dp</dimen>
     <dimen name="empty_list_progress_layout_width">72dp</dimen>
     <dimen name="empty_list_progress_layout_height">72dp</dimen>
-    <dimen name="shared_list_icon_width">14dp</dimen>
-    <dimen name="shared_list_icon_height">14dp</dimen>
-    <dimen name="share_no_users_text_size">14sp</dimen>
     <dimen name="file_details_user_icon_layout_width">32dp</dimen>
     <dimen name="file_details_user_icon_layout_height">32dp</dimen>
     <dimen name="file_details_username_text_size">16sp</dimen>

+ 16 - 12
src/main/res/values/strings.xml

@@ -113,7 +113,6 @@
     <string name="upload_list_empty_headline">No uploads available</string>
     <string name="upload_list_empty_text_auto_upload">Upload some content or activate auto upload.</string>
     <string name="file_list_folder">folder</string>
-    <string name="filedetails_select_file">Tap on a file to display additional information.</string>
     <string name="filedetails_download">Download</string>
     <string name="filedetails_sync_file">Sync</string>
     <string name="filedetails_renamed_in_upload_msg">File renamed %1$s during upload</string>
@@ -132,6 +131,7 @@
     <string name="common_error_unknown">Unknown error</string>
     <string name="common_pending">Pending</string>
     <string name="common_delete">Delete</string>
+    <string name="common_send">Send</string>
     <string name="about_title">About</string>
     <string name="delete_account">Remove account</string>
     <string name="delete_account_warning">Remove account %s and delete all local files?\n\nDeletion cannot be undone.</string>
@@ -478,17 +478,20 @@
     <string name="share_with_user_section_title">Share with users and groups</string>
     <string name="share_no_users">No data shared with users yet</string>
     <string name="share_add_user_or_group">Add user or group</string>
+    <string name="share_via_link_menu_password_label">Password protect (%1$s)</string>
+    <string name="share_expiration_date_label">Expires %1$s</string>
+    <string name="share_no_expiration_date_label">Set expiration date</string>
     <string name="share_via_link_section_title">Share link</string>
-    <string name="share_via_link_expiration_date_label">Set expiration date</string>
-    <string name="share_via_link_password_label">Protect with password</string>
-    <string name="share_via_link_password_title">Secured</string>
-    <string name="share_via_link_edit_permission_label">Allow edits</string>
+    <string name="share_password_title">Password-protected</string>
+    <string name="share_no_password_title">Set password</string>
+    <string name="edit_permission_label">edit</string>
     <string name="share_via_link_hide_file_listing_permission_label">Hide file listing</string>
     <string name="share_get_public_link_button">Get link</string>
     <string name="share_with_title">Share with…</string>
     <string name="share_with_edit_title">Share with %1$s</string>
+    <string name="share_via_link_unset_password">Unset</string>
 
-    <string name="share_search">Search</string>
+    <string name="share_search">Name, federated cloud ID or email address…</string>
 
     <string name="search_users_and_groups_hint">Search users and groups</string>
     <string name="share_group_clarification">%1$s (group)</string>
@@ -497,14 +500,12 @@
     <string name="share_known_remote_clarification">%1$s ( at %2$s )</string>
 
     <string name="share_sharee_unavailable">Upgrade the server version to allow sharing between users from within their clients.\nPlease contact your admin</string>
-    <string name="filedetails_share_link_enable">Share by link enabled</string>
-    <string name="filedetails_share_link_disable">Not shared by link</string>
-    <string name="filedetails_share_users_with_access">Users and groups with access</string>
+    <string name="share_privilege_unshare">Unshare</string>
     <string name="share_privilege_can_share">can share</string>
     <string name="share_privilege_can_edit">can edit</string>
-    <string name="share_privilege_can_edit_create">create</string>
-    <string name="share_privilege_can_edit_change">change</string>
-    <string name="share_privilege_can_edit_delete">delete</string>
+    <string name="share_privilege_can_edit_create">can create</string>
+    <string name="share_privilege_can_edit_change">can change</string>
+    <string name="share_privilege_can_edit_delete">can delete</string>
 
     <string name="action_retry_uploads">Retry failed uploads</string>
     <string name="action_clear_failed_uploads">Clear failed uploads</string>
@@ -784,4 +785,7 @@
     <string name="create_new_folder">Create new folder</string>
     <string name="uploads_view_upload_status_virus_detected">Virus detected. Upload cannot be completed!</string>
     <string name="tags">Tags</string>
+    <string name="sharee_add_failed">Adding sharee failed</string>
+    <string name="unsharing_failed">Unsharing failed</string>
+    <string name="updating_share_failed">Updating share failed</string>
 </resources>

+ 11 - 0
src/main/res/values/styles.xml

@@ -39,6 +39,12 @@
 		<item name="colorAccent">#757575</item>
 	</style>
 
+	<style name="FallbackDatePickerDialogTheme" parent="Theme.AppCompat.Light.Dialog">
+		<item name="colorPrimary">#424242</item>
+		<item name="colorPrimaryDark">#212121</item>
+		<item name="colorAccent">#757575</item>
+	</style>
+
 	<!-- seperate action bar style for activities without an action bar -->
 	<style name="Theme.ownCloud.Toolbar" parent="Theme.AppCompat.Light.NoActionBar">
 		<item name="windowNoTitle">true</item>
@@ -250,4 +256,9 @@
 		<item name="colorControlNormal">@color/login_text_color</item>
 		<item name="android:textColorHint">@color/login_text_color</item>
 	</style>
+
+	<style name="AppTabTextAppearance" parent="@style/TextAppearance.Design.Tab">
+		<item name="android:textSize">16sp</item>
+		<item name="textAllCaps">false</item>
+	</style>
 </resources>