Bladeren bron

Sharing dialog improvement

changed overflow menu items for sharing files and folders

completed NMCLOUD-350 for share permissions and fixed test cases as per new changes

permission menu code optimisation

added permission display to share fragment list items

fixed permission issue for files, added strings for german

changed sharing process for internal and external share by creating new full screen permission and note ui.
created new remote operation to share the file/folder with permissions

changed sharing menu options from popup to bottom sheet for all shares, modified options for link share

test cases written for updated sharing process

updated PR as per Tobias feedback:
1. Failed test cases reverted
2. Quick permissions sheet modified
3. Minor UI changes done.
4. Commented code removed

copyright header added

Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
A117870935 4 jaren geleden
bovenliggende
commit
df6d590196
39 gewijzigde bestanden met toevoegingen van 2938 en 684 verwijderingen
  1. 363 247
      src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailSharingFragmentIT.kt
  2. 3 2
      src/main/java/com/nextcloud/client/files/DeepLinkHandler.kt
  3. 1 0
      src/main/java/com/nextcloud/ui/SetStatusDialogFragment.kt
  4. 24 0
      src/main/java/com/owncloud/android/datamodel/QuickPermissionModel.kt
  5. 64 4
      src/main/java/com/owncloud/android/operations/CreateShareWithShareeOperation.java
  6. 151 0
      src/main/java/com/owncloud/android/operations/UpdateShareInfoOperation.java
  7. 103 57
      src/main/java/com/owncloud/android/services/OperationsService.java
  8. 11 1
      src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java
  9. 40 17
      src/main/java/com/owncloud/android/ui/activity/FileActivity.java
  10. 18 1
      src/main/java/com/owncloud/android/ui/adapter/LinkShareViewHolder.java
  11. 81 0
      src/main/java/com/owncloud/android/ui/adapter/QuickSharingPermissionsAdapter.kt
  12. 19 1
      src/main/java/com/owncloud/android/ui/adapter/ShareViewHolder.java
  13. 5 5
      src/main/java/com/owncloud/android/ui/adapter/ShareeListAdapterListener.java
  14. 23 19
      src/main/java/com/owncloud/android/ui/dialog/ExpirationDatePickerDialogFragment.java
  15. 54 0
      src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java
  16. 81 286
      src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java
  17. 124 0
      src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingMenuBottomSheetDialog.java
  18. 61 0
      src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingMenuBottomSheetActions.java
  19. 536 0
      src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt
  20. 164 0
      src/main/java/com/owncloud/android/ui/fragment/QuickSharingPermissionsBottomSheetDialog.java
  21. 91 3
      src/main/java/com/owncloud/android/ui/fragment/util/SharingMenuHelper.java
  22. 75 1
      src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java
  23. 30 0
      src/main/res/drawable/ic_baseline_arrow_drop_down_24.xml
  24. 30 0
      src/main/res/drawable/ic_baseline_check_24.xml
  25. 9 0
      src/main/res/layout/file_details_fragment.xml
  26. 36 12
      src/main/res/layout/file_details_share_link_share_item.xml
  27. 48 25
      src/main/res/layout/file_details_share_share_item.xml
  28. 222 0
      src/main/res/layout/file_details_sharing_menu_bottom_sheet_fragment.xml
  29. 279 0
      src/main/res/layout/file_details_sharing_process_fragment.xml
  30. 52 0
      src/main/res/layout/item_quick_share_permissions.xml
  31. 36 0
      src/main/res/layout/quick_sharing_permissions_bottom_sheet_fragment.xml
  32. 3 0
      src/main/res/menu/fragment_file_detail_sharing_email_link.xml
  33. 4 0
      src/main/res/menu/fragment_file_detail_sharing_public_link.xml
  34. 21 0
      src/main/res/menu/item_user_sharing_settings.xml
  35. 20 0
      src/main/res/values-b+en+001/strings.xml
  36. 23 3
      src/main/res/values-de/strings.xml
  37. 11 0
      src/main/res/values/attrs.xml
  38. 21 0
      src/main/res/values/strings.xml
  39. 1 0
      src/test/java/com/owncloud/android/ui/adapter/ShareeListAdapterTest.kt

+ 363 - 247
src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailSharingFragmentIT.kt

@@ -3,8 +3,10 @@
  * Nextcloud Android client application
  *
  * @author Tobias Kaminsky
+ * @author TSI-mc
  * Copyright (C) 2020 Tobias Kaminsky
  * Copyright (C) 2020 Nextcloud GmbH
+ * Copyright (C) 2021 TSI-mc
  *
  * 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
@@ -21,9 +23,15 @@
  */
 package com.owncloud.android.ui.fragment
 
-import android.widget.ImageView
-import androidx.appcompat.widget.PopupMenu
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions
+import androidx.test.espresso.assertion.ViewAssertions.matches
 import androidx.test.espresso.intent.rule.IntentsTestRule
+import androidx.test.espresso.matcher.ViewMatchers
+import androidx.test.espresso.matcher.ViewMatchers.isChecked
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.isNotChecked
+import androidx.test.espresso.matcher.ViewMatchers.withText
 import com.nextcloud.client.TestActivity
 import com.owncloud.android.AbstractIT
 import com.owncloud.android.R
@@ -37,7 +45,9 @@ import com.owncloud.android.lib.resources.shares.OCShare.NO_PERMISSION
 import com.owncloud.android.lib.resources.shares.OCShare.READ_PERMISSION_FLAG
 import com.owncloud.android.lib.resources.shares.OCShare.SHARE_PERMISSION_FLAG
 import com.owncloud.android.lib.resources.shares.ShareType
+import com.owncloud.android.ui.fragment.util.SharingMenuHelper
 import com.owncloud.android.utils.ScreenshotTest
+import org.hamcrest.CoreMatchers.not
 import org.junit.After
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
@@ -45,6 +55,7 @@ import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 
+@Suppress("TooManyFunctions")
 class FileDetailSharingFragmentIT : AbstractIT() {
     @get:Rule
     val testActivityRule = IntentsTestRule(TestActivity::class.java, true, false)
@@ -85,9 +96,9 @@ class FileDetailSharingFragmentIT : AbstractIT() {
     @Test
     @ScreenshotTest
     @Suppress("MagicNumber")
-    /**
-     * Use same values as {@link OCFileListFragmentStaticServerIT showSharedFiles }
-     */
+        /**
+         * Use same values as {@link OCFileListFragmentStaticServerIT showSharedFiles }
+         */
     fun listSharesFileAllShareTypes() {
         OCShare(file.decryptedRemotePath).apply {
             remoteId = 1
@@ -208,240 +219,223 @@ class FileDetailSharingFragmentIT : AbstractIT() {
     @Test
     @Suppress("MagicNumber")
     // public link and email are handled the same way
-    fun publicLinkOptionMenuFolder() {
+    // for advanced permissions
+    fun publicLinkOptionMenuFolderAdvancePermission() {
         val sut = FileDetailSharingFragment.newInstance(file, user)
         activity.addFragment(sut)
         shortSleep()
         sut.refreshCapabilitiesFromDB()
 
-        val overflowMenuShareLink = ImageView(targetContext)
-        val popup = PopupMenu(targetContext, overflowMenuShareLink)
-        popup.inflate(R.menu.fragment_file_detail_sharing_public_link)
         val publicShare = OCShare().apply {
             isFolder = true
             shareType = ShareType.PUBLIC_LINK
             permissions = 17
         }
 
-        sut.prepareLinkOptionsMenu(popup.menu, publicShare)
+        activity.runOnUiThread { sut.showSharingMenuActionSheet(publicShare) }
 
         // check if items are visible
-        assertTrue(popup.menu.findItem(R.id.action_hide_file_download).isVisible)
-        assertTrue(popup.menu.findItem(R.id.action_password).isVisible)
-        assertTrue(popup.menu.findItem(R.id.action_share_expiration_date).isVisible)
-        assertTrue(popup.menu.findItem(R.id.action_share_send_link).isVisible)
-        assertTrue(popup.menu.findItem(R.id.action_share_send_note).isVisible)
-        assertTrue(popup.menu.findItem(R.id.action_edit_label).isVisible)
-        assertTrue(popup.menu.findItem(R.id.action_unshare).isVisible)
-        assertTrue(popup.menu.findItem(R.id.action_add_another_public_share_link).isVisible)
+        onView(ViewMatchers.withId(R.id.menu_share_open_in)).check(matches(not(isDisplayed())))
+        onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.menu_share_send_new_email)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.menu_share_send_link)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.menu_share_unshare)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.menu_share_add_another_link)).check(matches(isDisplayed()))
+
+        //click event
+        onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).perform(ViewActions.click())
+
+        //validate view shown on screen
+        onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.share_process_permission_file_drop)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.share_process_hide_download_checkbox)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.share_process_change_name_switch)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.share_process_allow_resharing_checkbox)).check(matches(not(isDisplayed())))
 
         // read-only
-        assertTrue(popup.menu.findItem(R.id.link_share_read_only).isChecked)
-        assertFalse(popup.menu.findItem(R.id.link_share_allow_upload_and_editing).isChecked)
-        assertFalse(popup.menu.findItem(R.id.link_share_file_drop).isChecked)
+        onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isChecked()))
+        onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isNotChecked()))
+        onView(ViewMatchers.withId(R.id.share_process_permission_file_drop)).check(matches(isNotChecked()))
+        goBack()
 
         // upload and editing
         publicShare.permissions = MAXIMUM_PERMISSIONS_FOR_FOLDER
-        sut.prepareLinkOptionsMenu(popup.menu, publicShare)
-        assertFalse(popup.menu.findItem(R.id.link_share_read_only).isChecked)
-        assertTrue(popup.menu.findItem(R.id.link_share_allow_upload_and_editing).isChecked)
-        assertFalse(popup.menu.findItem(R.id.link_share_file_drop).isChecked)
+        openAdvancedPermissions(sut, publicShare)
+        onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isNotChecked()))
+        onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isChecked()))
+        onView(ViewMatchers.withId(R.id.share_process_permission_file_drop)).check(matches(isNotChecked()))
+        goBack()
 
         // file drop
         publicShare.permissions = 4
-        sut.prepareLinkOptionsMenu(popup.menu, publicShare)
-        assertFalse(popup.menu.findItem(R.id.link_share_read_only).isChecked)
-        assertFalse(popup.menu.findItem(R.id.link_share_allow_upload_and_editing).isChecked)
-        assertTrue(popup.menu.findItem(R.id.link_share_file_drop).isChecked)
+        openAdvancedPermissions(sut, publicShare)
+        onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isNotChecked()))
+        onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isNotChecked()))
+        onView(ViewMatchers.withId(R.id.share_process_permission_file_drop)).check(matches(isChecked()))
+        goBack()
 
         // password protection
         publicShare.shareWith = "someValue"
-        sut.prepareLinkOptionsMenu(popup.menu, publicShare)
-        assertTrue(
-            popup.menu.findItem(R.id.action_password).title == targetContext.getString(R.string.share_password_title)
-        )
+        openAdvancedPermissions(sut, publicShare)
+        onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(isChecked()))
+        goBack()
 
         publicShare.shareWith = ""
-        sut.prepareLinkOptionsMenu(popup.menu, publicShare)
-        assertTrue(
-            popup.menu.findItem(R.id.action_password).title == targetContext.getString(R.string.share_no_password_title)
-        )
+        openAdvancedPermissions(sut, publicShare)
+        onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(isNotChecked()))
+        goBack()
 
         // hide download
         publicShare.isHideFileDownload = true
         publicShare.permissions = MAXIMUM_PERMISSIONS_FOR_FOLDER
-        sut.prepareLinkOptionsMenu(popup.menu, publicShare)
-        assertTrue(popup.menu.findItem(R.id.action_hide_file_download).isChecked)
+        openAdvancedPermissions(sut, publicShare)
+        onView(ViewMatchers.withId(R.id.share_process_hide_download_checkbox)).check(matches(isChecked()))
+        goBack()
 
         publicShare.isHideFileDownload = false
-        sut.prepareLinkOptionsMenu(popup.menu, publicShare)
-        assertFalse(popup.menu.findItem(R.id.action_hide_file_download).isChecked)
+        openAdvancedPermissions(sut, publicShare)
+        onView(ViewMatchers.withId(R.id.share_process_hide_download_checkbox)).check(matches(isNotChecked()))
+        goBack()
 
         publicShare.expirationDate = 1582019340000
-        sut.prepareLinkOptionsMenu(popup.menu, publicShare)
-        assertTrue(
-            popup.menu.findItem(R.id.action_share_expiration_date).title
-                .startsWith(targetContext.getString(R.string.share_expiration_date_label).split(" ")[0])
-        )
+        openAdvancedPermissions(sut, publicShare)
+        onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isChecked()))
+        onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(not(withText(""))))
+        goBack()
 
         publicShare.expirationDate = 0
-        sut.prepareLinkOptionsMenu(popup.menu, publicShare)
-        assertTrue(
-            popup.menu.findItem(R.id.action_share_expiration_date).title ==
-                targetContext.getString(R.string.share_no_expiration_date_label)
-        )
-
-        // file
-        publicShare.isFolder = false
-        publicShare.permissions = READ_PERMISSION_FLAG
-        sut.prepareLinkOptionsMenu(popup.menu, publicShare)
+        openAdvancedPermissions(sut, publicShare)
+        onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isNotChecked()))
+        onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(withText("")))
+    }
+
+    @Test
+    @Suppress("MagicNumber")
+    // public link and email are handled the same way
+    // for send new email
+    fun publicLinkOptionMenuFolderSendNewEmail() {
+        val sut = FileDetailSharingFragment.newInstance(file, user)
+        activity.addFragment(sut)
+        shortSleep()
+        sut.refreshCapabilitiesFromDB()
+
+        val publicShare = OCShare().apply {
+            isFolder = true
+            shareType = ShareType.PUBLIC_LINK
+            permissions = 17
+        }
+
+        verifySendNewEmail(sut, publicShare)
+    }
+
+    @Test
+    @Suppress("MagicNumber")
+    // public link and email are handled the same way
+    // for advanced permissions
+    fun publicLinkOptionMenuFileAdvancePermission() {
+        val sut = FileDetailSharingFragment.newInstance(file, user)
+        activity.addFragment(sut)
+        shortSleep()
+        sut.refreshCapabilitiesFromDB()
+
+        val publicShare = OCShare().apply {
+            isFolder = false
+            shareType = ShareType.PUBLIC_LINK
+            permissions = 17
+        }
+        activity.runOnUiThread { sut.showSharingMenuActionSheet(publicShare) }
+
         // check if items are visible
-        assertTrue(popup.menu.findItem(R.id.action_hide_file_download).isVisible)
-        assertTrue(popup.menu.findItem(R.id.action_password).isVisible)
-        assertTrue(popup.menu.findItem(R.id.action_share_expiration_date).isVisible)
-        assertTrue(popup.menu.findItem(R.id.action_share_send_link).isVisible)
-        assertTrue(popup.menu.findItem(R.id.action_share_send_note).isVisible)
-        assertTrue(popup.menu.findItem(R.id.action_edit_label).isVisible)
-        assertTrue(popup.menu.findItem(R.id.action_unshare).isVisible)
-        assertTrue(popup.menu.findItem(R.id.action_add_another_public_share_link).isVisible)
-
-        assertFalse(popup.menu.findItem(R.id.link_share_read_only).isVisible)
-        assertFalse(popup.menu.findItem(R.id.link_share_allow_upload_and_editing).isVisible)
-        assertFalse(popup.menu.findItem(R.id.link_share_file_drop).isVisible)
-        assertTrue(popup.menu.findItem(R.id.allow_editing).isVisible)
-
-        // allow editing
+        onView(ViewMatchers.withId(R.id.menu_share_open_in)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.menu_share_send_new_email)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.menu_share_send_link)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.menu_share_unshare)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.menu_share_add_another_link)).check(matches(isDisplayed()))
+
+        //click event
+        onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).perform(ViewActions.click())
+
+        //validate view shown on screen
+        onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.share_process_permission_file_drop)).check(matches(not(isDisplayed())))
+        onView(ViewMatchers.withId(R.id.share_process_hide_download_checkbox)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.share_process_change_name_switch)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.share_process_allow_resharing_checkbox)).check(matches(not(isDisplayed())))
+
+        // read-only
         publicShare.permissions = 17 // from server
-        sut.prepareLinkOptionsMenu(popup.menu, publicShare)
-        assertFalse(popup.menu.findItem(R.id.allow_editing).isChecked)
+        onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isChecked()))
+        onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isNotChecked()))
+        goBack()
 
-        publicShare.permissions = 19 // from server
-        sut.prepareLinkOptionsMenu(popup.menu, publicShare)
-        assertTrue(popup.menu.findItem(R.id.allow_editing).isChecked)
+        // editing
+        publicShare.permissions = MAXIMUM_PERMISSIONS_FOR_FILE // from server
+        openAdvancedPermissions(sut, publicShare)
+        onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isNotChecked()))
+        onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isChecked()))
+        goBack()
 
         // hide download
         publicShare.isHideFileDownload = true
-        sut.prepareLinkOptionsMenu(popup.menu, publicShare)
-        assertTrue(popup.menu.findItem(R.id.action_hide_file_download).isChecked)
+        openAdvancedPermissions(sut, publicShare)
+        onView(ViewMatchers.withId(R.id.share_process_hide_download_checkbox)).check(matches(isChecked()))
+        goBack()
 
         publicShare.isHideFileDownload = false
-        sut.prepareLinkOptionsMenu(popup.menu, publicShare)
-        assertFalse(popup.menu.findItem(R.id.action_hide_file_download).isChecked)
+        openAdvancedPermissions(sut, publicShare)
+        onView(ViewMatchers.withId(R.id.share_process_hide_download_checkbox)).check(matches(isNotChecked()))
+        goBack()
 
         // password protection
         publicShare.isPasswordProtected = true
         publicShare.shareWith = "someValue"
-        sut.prepareLinkOptionsMenu(popup.menu, publicShare)
-        assertTrue(
-            popup.menu.findItem(R.id.action_password).title == targetContext.getString(R.string.share_password_title)
-        )
+        openAdvancedPermissions(sut, publicShare)
+        onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(isChecked()))
+        goBack()
 
         publicShare.isPasswordProtected = false
         publicShare.shareWith = ""
-        sut.prepareLinkOptionsMenu(popup.menu, publicShare)
-        assertTrue(
-            popup.menu.findItem(R.id.action_password).title == targetContext.getString(R.string.share_no_password_title)
-        )
+        openAdvancedPermissions(sut, publicShare)
+        onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(isNotChecked()))
+        goBack()
 
         // expires
         publicShare.expirationDate = 1582019340
-        sut.prepareLinkOptionsMenu(popup.menu, publicShare)
-        assertTrue(
-            popup.menu.findItem(R.id.action_share_expiration_date).title
-                .startsWith(targetContext.getString(R.string.share_expiration_date_label).split(" ")[0])
-        )
+        openAdvancedPermissions(sut, publicShare)
+        onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isChecked()))
+        onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(not(withText(""))))
+        goBack()
 
         publicShare.expirationDate = 0
-        sut.prepareLinkOptionsMenu(popup.menu, publicShare)
-        assertTrue(
-            popup.menu.findItem(R.id.action_share_expiration_date).title ==
-                targetContext.getString(R.string.share_no_expiration_date_label)
-        )
+        openAdvancedPermissions(sut, publicShare)
+        onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isNotChecked()))
+        onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(withText("")))
     }
 
     @Test
     @Suppress("MagicNumber")
     // public link and email are handled the same way
-    fun publicLinkOptionMenuFile() {
+    // for send new email
+    fun publicLinkOptionMenuFileSendNewEmail() {
         val sut = FileDetailSharingFragment.newInstance(file, user)
         activity.addFragment(sut)
         shortSleep()
         sut.refreshCapabilitiesFromDB()
 
-        val overflowMenuShareLink = ImageView(targetContext)
-        val popup = PopupMenu(targetContext, overflowMenuShareLink)
-        popup.inflate(R.menu.fragment_file_detail_sharing_public_link)
         val publicShare = OCShare().apply {
             isFolder = false
             shareType = ShareType.PUBLIC_LINK
             permissions = 17
         }
 
-        sut.prepareLinkOptionsMenu(popup.menu, publicShare)
-
-        // check if items are visible
-        assertTrue(popup.menu.findItem(R.id.action_hide_file_download).isVisible)
-        assertTrue(popup.menu.findItem(R.id.action_password).isVisible)
-        assertTrue(popup.menu.findItem(R.id.action_share_expiration_date).isVisible)
-        assertTrue(popup.menu.findItem(R.id.action_share_send_link).isVisible)
-        assertTrue(popup.menu.findItem(R.id.action_share_send_note).isVisible)
-        assertTrue(popup.menu.findItem(R.id.action_edit_label).isVisible)
-        assertTrue(popup.menu.findItem(R.id.action_unshare).isVisible)
-        assertTrue(popup.menu.findItem(R.id.action_add_another_public_share_link).isVisible)
-
-        assertFalse(popup.menu.findItem(R.id.link_share_read_only).isVisible)
-        assertFalse(popup.menu.findItem(R.id.link_share_allow_upload_and_editing).isVisible)
-        assertFalse(popup.menu.findItem(R.id.link_share_file_drop).isVisible)
-        assertTrue(popup.menu.findItem(R.id.allow_editing).isVisible)
-
-        // password protection
-        publicShare.shareWith = "someValue"
-        sut.prepareLinkOptionsMenu(popup.menu, publicShare)
-        assertTrue(
-            popup.menu.findItem(R.id.action_password).title == targetContext.getString(R.string.share_password_title)
-        )
-
-        publicShare.shareWith = ""
-        sut.prepareLinkOptionsMenu(popup.menu, publicShare)
-        assertTrue(
-            popup.menu.findItem(R.id.action_password).title == targetContext.getString(R.string.share_no_password_title)
-        )
-
-        // hide download
-        publicShare.isHideFileDownload = true
-        sut.prepareLinkOptionsMenu(popup.menu, publicShare)
-        assertTrue(popup.menu.findItem(R.id.action_hide_file_download).isChecked)
-
-        publicShare.isHideFileDownload = false
-        sut.prepareLinkOptionsMenu(popup.menu, publicShare)
-        assertFalse(popup.menu.findItem(R.id.action_hide_file_download).isChecked)
-
-        // expiration date
-        publicShare.expirationDate = 1582019340000
-        sut.prepareLinkOptionsMenu(popup.menu, publicShare)
-        assertTrue(
-            popup.menu.findItem(R.id.action_share_expiration_date).title
-                .startsWith(targetContext.getString(R.string.share_expiration_date_label).split(" ")[0])
-        )
-
-        publicShare.expirationDate = 0
-        sut.prepareLinkOptionsMenu(popup.menu, publicShare)
-        assertTrue(
-            popup.menu.findItem(R.id.action_share_expiration_date).title ==
-                targetContext.getString(R.string.share_no_expiration_date_label)
-        )
-
-        publicShare.isFolder = false
-        publicShare.permissions = READ_PERMISSION_FLAG
-        sut.prepareLinkOptionsMenu(popup.menu, publicShare)
-
-        // allow editing
-        publicShare.permissions = 17 // from server
-        assertFalse(popup.menu.findItem(R.id.allow_editing).isChecked)
-
-        publicShare.permissions = 19 // from server
-        sut.prepareLinkOptionsMenu(popup.menu, publicShare)
-        assertTrue(popup.menu.findItem(R.id.allow_editing).isChecked)
+        verifySendNewEmail(sut, publicShare)
     }
 
     @Test
@@ -451,59 +445,100 @@ class FileDetailSharingFragmentIT : AbstractIT() {
     // conversation
     // circle
     // federated share
-    fun userOptionMenuFile() {
+    // for advanced permissions
+    fun userOptionMenuFileAdvancePermission() {
         val sut = FileDetailSharingFragment.newInstance(file, user)
         activity.addFragment(sut)
         shortSleep()
         sut.refreshCapabilitiesFromDB()
 
-        val overflowMenuShareLink = ImageView(targetContext)
-        val popup = PopupMenu(targetContext, overflowMenuShareLink)
-        popup.inflate(R.menu.item_user_sharing_settings)
         val userShare = OCShare().apply {
             isFolder = false
             shareType = ShareType.USER
             permissions = 17
         }
 
-        sut.prepareUserOptionsMenu(popup.menu, userShare)
-        assertFalse(popup.menu.findItem(R.id.allow_creating).isVisible)
-        assertFalse(popup.menu.findItem(R.id.allow_deleting).isVisible)
+        activity.runOnUiThread { sut.showSharingMenuActionSheet(userShare) }
 
-        // allow editing
+        // check if items are visible
+        onView(ViewMatchers.withId(R.id.menu_share_open_in)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.menu_share_send_new_email)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.menu_share_send_link)).check(matches(not(isDisplayed())))
+        onView(ViewMatchers.withId(R.id.menu_share_unshare)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.menu_share_add_another_link)).check(matches(not(isDisplayed())))
+
+        //click event
+        onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).perform(ViewActions.click())
+
+        //validate view shown on screen
+        onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.share_process_permission_file_drop)).check(matches(not(isDisplayed())))
+        onView(ViewMatchers.withId(R.id.share_process_hide_download_checkbox)).check(matches(not(isDisplayed())))
+        onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(not(isDisplayed())))
+        onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.share_process_change_name_switch)).check(matches(not(isDisplayed())))
+        onView(ViewMatchers.withId(R.id.share_process_allow_resharing_checkbox)).check(matches(isDisplayed()))
+
+        // read-only
         userShare.permissions = 17 // from server
-        assertFalse(popup.menu.findItem(R.id.allow_editing).isChecked)
+        onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isChecked()))
+        onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isNotChecked()))
+        goBack()
 
-        userShare.permissions = 19 // from server
-        sut.prepareUserOptionsMenu(popup.menu, userShare)
-        assertTrue(popup.menu.findItem(R.id.allow_editing).isChecked)
+        // editing
+        userShare.permissions = MAXIMUM_PERMISSIONS_FOR_FILE // from server
+        openAdvancedPermissions(sut, userShare)
+        onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isNotChecked()))
+        onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isChecked()))
+        goBack()
 
         // allow reshare
         userShare.permissions = 1 // from server
-        sut.prepareUserOptionsMenu(popup.menu, userShare)
-        assertFalse(popup.menu.findItem(R.id.allow_resharing).isChecked)
+        openAdvancedPermissions(sut, userShare)
+        onView(ViewMatchers.withId(R.id.share_process_allow_resharing_checkbox)).check(matches(isNotChecked()))
+        goBack()
 
         userShare.permissions = 17 // from server
-        sut.prepareUserOptionsMenu(popup.menu, userShare)
-        assertTrue(popup.menu.findItem(R.id.allow_resharing).isChecked)
+        openAdvancedPermissions(sut, userShare)
+        onView(ViewMatchers.withId(R.id.share_process_allow_resharing_checkbox)).check(matches(isChecked()))
+        goBack()
 
         // set expiration date
         userShare.expirationDate = 1582019340000
-        sut.prepareUserOptionsMenu(popup.menu, userShare)
-        assertTrue(
-            popup.menu.findItem(R.id.action_expiration_date).title
-                .startsWith(targetContext.getString(R.string.share_expiration_date_label).split(" ")[0])
-        )
+        openAdvancedPermissions(sut, userShare)
+        onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isChecked()))
+        onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(not(withText(""))))
+        goBack()
 
         userShare.expirationDate = 0
-        sut.prepareUserOptionsMenu(popup.menu, userShare)
-        assertTrue(
-            popup.menu.findItem(R.id.action_expiration_date).title ==
-                targetContext.getString(R.string.share_no_expiration_date_label)
-        )
-
-        // note
-        assertTrue(popup.menu.findItem(R.id.action_share_send_note).isVisible)
+        openAdvancedPermissions(sut, userShare)
+        onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isNotChecked()))
+        onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(withText("")))
+    }
+
+    @Test
+    @Suppress("MagicNumber")
+    // also applies for
+    // group
+    // conversation
+    // circle
+    // federated share
+    // for send new email
+    fun userOptionMenuFileSendNewEmail() {
+        val sut = FileDetailSharingFragment.newInstance(file, user)
+        activity.addFragment(sut)
+        shortSleep()
+        sut.refreshCapabilitiesFromDB()
+
+        val userShare = OCShare().apply {
+            isFolder = false
+            shareType = ShareType.USER
+            permissions = 17
+        }
+
+        verifySendNewEmail(sut, userShare)
     }
 
     @Test
@@ -513,59 +548,140 @@ class FileDetailSharingFragmentIT : AbstractIT() {
     // conversation
     // circle
     // federated share
-    fun userOptionMenuFolder() {
+    // for advanced permissions
+    fun userOptionMenuFolderAdvancePermission() {
         val sut = FileDetailSharingFragment.newInstance(file, user)
         activity.addFragment(sut)
         shortSleep()
         sut.refreshCapabilitiesFromDB()
 
-        val overflowMenuShareLink = ImageView(targetContext)
-        val popup = PopupMenu(targetContext, overflowMenuShareLink)
-        popup.inflate(R.menu.item_user_sharing_settings)
         val userShare = OCShare().apply {
             isFolder = true
             shareType = ShareType.USER
             permissions = 17
         }
 
-        sut.prepareUserOptionsMenu(popup.menu, userShare)
-        assertTrue(popup.menu.findItem(R.id.allow_creating).isVisible)
-        assertTrue(popup.menu.findItem(R.id.allow_deleting).isVisible)
+        activity.runOnUiThread { sut.showSharingMenuActionSheet(userShare) }
+
+        // check if items are visible
+        onView(ViewMatchers.withId(R.id.menu_share_open_in)).check(matches(not(isDisplayed())))
+        onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.menu_share_send_new_email)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.menu_share_send_link)).check(matches(not(isDisplayed())))
+        onView(ViewMatchers.withId(R.id.menu_share_unshare)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.menu_share_add_another_link)).check(matches(not(isDisplayed())))
+
+        //click event
+        onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).perform(ViewActions.click())
+
+        //validate view shown on screen
+        onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.share_process_permission_file_drop)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.share_process_hide_download_checkbox)).check(matches(not(isDisplayed())))
+        onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(not(isDisplayed())))
+        onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isDisplayed()))
+        onView(ViewMatchers.withId(R.id.share_process_change_name_switch)).check(matches(not(isDisplayed())))
+        onView(ViewMatchers.withId(R.id.share_process_allow_resharing_checkbox)).check(matches(isDisplayed()))
 
-        // allow editing
+        // read-only
         userShare.permissions = 17 // from server
-        assertFalse(popup.menu.findItem(R.id.allow_editing).isChecked)
+        onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isChecked()))
+        onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isNotChecked()))
+        onView(ViewMatchers.withId(R.id.share_process_permission_file_drop)).check(matches(isNotChecked()))
+        goBack()
+
+        // allow upload & editing
+        userShare.permissions = MAXIMUM_PERMISSIONS_FOR_FOLDER // from server
+        openAdvancedPermissions(sut, userShare)
+        onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isNotChecked()))
+        onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isChecked()))
+        onView(ViewMatchers.withId(R.id.share_process_permission_file_drop)).check(matches(isNotChecked()))
+        goBack()
 
-        userShare.permissions = 19 // from server
-        sut.prepareUserOptionsMenu(popup.menu, userShare)
-        assertTrue(popup.menu.findItem(R.id.allow_editing).isChecked)
+        // file drop
+        userShare.permissions = 4
+        openAdvancedPermissions(sut, userShare)
+        onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isNotChecked()))
+        onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isNotChecked()))
+        onView(ViewMatchers.withId(R.id.share_process_permission_file_drop)).check(matches(isChecked()))
+        goBack()
 
         // allow reshare
         userShare.permissions = 1 // from server
-        sut.prepareUserOptionsMenu(popup.menu, userShare)
-        assertFalse(popup.menu.findItem(R.id.allow_resharing).isChecked)
+        openAdvancedPermissions(sut, userShare)
+        onView(ViewMatchers.withId(R.id.share_process_allow_resharing_checkbox)).check(matches(isNotChecked()))
+        goBack()
 
         userShare.permissions = 17 // from server
-        sut.prepareUserOptionsMenu(popup.menu, userShare)
-        assertTrue(popup.menu.findItem(R.id.allow_resharing).isChecked)
+        openAdvancedPermissions(sut, userShare)
+        onView(ViewMatchers.withId(R.id.share_process_allow_resharing_checkbox)).check(matches(isChecked()))
+        goBack()
 
         // set expiration date
         userShare.expirationDate = 1582019340000
-        sut.prepareUserOptionsMenu(popup.menu, userShare)
-        assertTrue(
-            popup.menu.findItem(R.id.action_expiration_date).title
-                .startsWith(targetContext.getString(R.string.share_expiration_date_label).split(" ")[0])
-        )
+        openAdvancedPermissions(sut, userShare)
+        onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isChecked()))
+        onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(not(withText(""))))
+        goBack()
 
         userShare.expirationDate = 0
-        sut.prepareUserOptionsMenu(popup.menu, userShare)
-        assertTrue(
-            popup.menu.findItem(R.id.action_expiration_date).title ==
-                targetContext.getString(R.string.share_no_expiration_date_label)
-        )
-
-        // note
-        assertTrue(popup.menu.findItem(R.id.action_share_send_note).isVisible)
+        openAdvancedPermissions(sut, userShare)
+        onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isNotChecked()))
+        onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(withText("")))
+    }
+
+    //open bottom sheet with actions
+    private fun openAdvancedPermissions(
+        sut: FileDetailSharingFragment,
+        userShare: OCShare
+    ) {
+        activity.runOnUiThread { sut.showSharingMenuActionSheet(userShare) }
+        onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).perform(ViewActions.click())
+    }
+
+    //remove the fragment shown
+    private fun goBack() {
+        onView(ViewMatchers.withId(R.id.share_process_btn_cancel)).perform(ViewActions.click())
+    }
+
+    @Test
+    @Suppress("MagicNumber")
+    // also applies for
+    // group
+    // conversation
+    // circle
+    // federated share
+    // for send new email
+    fun userOptionMenuFolderSendNewEmail() {
+        val sut = FileDetailSharingFragment.newInstance(file, user)
+        activity.addFragment(sut)
+        shortSleep()
+        sut.refreshCapabilitiesFromDB()
+
+        val userShare = OCShare().apply {
+            isFolder = true
+            shareType = ShareType.USER
+            permissions = 17
+        }
+
+        verifySendNewEmail(sut, userShare)
+    }
+
+    /**
+     * verify send new email note text
+     */
+    private fun verifySendNewEmail(
+        sut: FileDetailSharingFragment,
+        userShare: OCShare
+    ) {
+        activity.runOnUiThread { sut.showSharingMenuActionSheet(userShare) }
+
+        //click event
+        onView(ViewMatchers.withId(R.id.menu_share_send_new_email)).perform(ViewActions.click())
+
+        //validate view shown on screen
+        onView(ViewMatchers.withId(R.id.note_text)).check(matches(isDisplayed()))
     }
 
     @Test
@@ -575,22 +691,22 @@ class FileDetailSharingFragmentIT : AbstractIT() {
         val share = OCShare().apply {
             permissions = MAXIMUM_PERMISSIONS_FOR_FOLDER
         }
-        assertTrue(sut.isUploadAndEditingAllowed(share))
+        assertTrue(SharingMenuHelper.isUploadAndEditingAllowed(share))
 
         share.permissions = NO_PERMISSION
-        assertFalse(sut.isUploadAndEditingAllowed(share))
+        assertFalse(SharingMenuHelper.isUploadAndEditingAllowed(share))
 
         share.permissions = READ_PERMISSION_FLAG
-        assertFalse(sut.isUploadAndEditingAllowed(share))
+        assertFalse(SharingMenuHelper.isUploadAndEditingAllowed(share))
 
         share.permissions = CREATE_PERMISSION_FLAG
-        assertFalse(sut.isUploadAndEditingAllowed(share))
+        assertFalse(SharingMenuHelper.isUploadAndEditingAllowed(share))
 
         share.permissions = DELETE_PERMISSION_FLAG
-        assertFalse(sut.isUploadAndEditingAllowed(share))
+        assertFalse(SharingMenuHelper.isUploadAndEditingAllowed(share))
 
         share.permissions = SHARE_PERMISSION_FLAG
-        assertFalse(sut.isUploadAndEditingAllowed(share))
+        assertFalse(SharingMenuHelper.isUploadAndEditingAllowed(share))
     }
 
     @Test
@@ -601,28 +717,28 @@ class FileDetailSharingFragmentIT : AbstractIT() {
         val share = OCShare().apply {
             permissions = 17
         }
-        assertTrue(sut.isReadOnly(share))
+        assertTrue(SharingMenuHelper.isReadOnly(share))
 
         share.permissions = NO_PERMISSION
-        assertFalse(sut.isReadOnly(share))
+        assertFalse(SharingMenuHelper.isReadOnly(share))
 
         share.permissions = READ_PERMISSION_FLAG
-        assertTrue(sut.isReadOnly(share))
+        assertTrue(SharingMenuHelper.isReadOnly(share))
 
         share.permissions = CREATE_PERMISSION_FLAG
-        assertFalse(sut.isReadOnly(share))
+        assertFalse(SharingMenuHelper.isReadOnly(share))
 
         share.permissions = DELETE_PERMISSION_FLAG
-        assertFalse(sut.isReadOnly(share))
+        assertFalse(SharingMenuHelper.isReadOnly(share))
 
         share.permissions = SHARE_PERMISSION_FLAG
-        assertFalse(sut.isReadOnly(share))
+        assertFalse(SharingMenuHelper.isReadOnly(share))
 
         share.permissions = MAXIMUM_PERMISSIONS_FOR_FOLDER
-        assertFalse(sut.isReadOnly(share))
+        assertFalse(SharingMenuHelper.isReadOnly(share))
 
         share.permissions = MAXIMUM_PERMISSIONS_FOR_FILE
-        assertFalse(sut.isReadOnly(share))
+        assertFalse(SharingMenuHelper.isReadOnly(share))
     }
 
     @Test
@@ -633,28 +749,28 @@ class FileDetailSharingFragmentIT : AbstractIT() {
         val share = OCShare().apply {
             permissions = 4
         }
-        assertTrue(sut.isFileDrop(share))
+        assertTrue(SharingMenuHelper.isFileDrop(share))
 
         share.permissions = NO_PERMISSION
-        assertFalse(sut.isFileDrop(share))
+        assertFalse(SharingMenuHelper.isFileDrop(share))
 
         share.permissions = READ_PERMISSION_FLAG
-        assertFalse(sut.isFileDrop(share))
+        assertFalse(SharingMenuHelper.isFileDrop(share))
 
         share.permissions = CREATE_PERMISSION_FLAG
-        assertTrue(sut.isFileDrop(share))
+        assertTrue(SharingMenuHelper.isFileDrop(share))
 
         share.permissions = DELETE_PERMISSION_FLAG
-        assertFalse(sut.isFileDrop(share))
+        assertFalse(SharingMenuHelper.isFileDrop(share))
 
         share.permissions = SHARE_PERMISSION_FLAG
-        assertFalse(sut.isFileDrop(share))
+        assertFalse(SharingMenuHelper.isFileDrop(share))
 
         share.permissions = MAXIMUM_PERMISSIONS_FOR_FOLDER
-        assertFalse(sut.isFileDrop(share))
+        assertFalse(SharingMenuHelper.isFileDrop(share))
 
         share.permissions = MAXIMUM_PERMISSIONS_FOR_FILE
-        assertFalse(sut.isFileDrop(share))
+        assertFalse(SharingMenuHelper.isFileDrop(share))
     }
 
     @After

+ 3 - 2
src/main/java/com/nextcloud/client/files/DeepLinkHandler.kt

@@ -1,4 +1,4 @@
-/**
+/*
  * Nextcloud Android client application
  *
  * @author Chris Narkiewicz
@@ -27,9 +27,10 @@ import com.nextcloud.client.account.UserAccountManager
  * This component parses and matches deep links.
  * Result is returned to the UI for further processing.
  *
- * TODO: This is intermediate refactring step; this component should be moved into
+ * TODO: This is intermediate refactoring step; this component should be moved into
  *       [com.nextcloud.client.mixins.ActivityMixin] and handle UI callbacks as well
  */
+@Suppress("ForbiddenComment")
 class DeepLinkHandler(
     private val userAccountManager: UserAccountManager
 ) {

+ 1 - 0
src/main/java/com/nextcloud/ui/SetStatusDialogFragment.kt

@@ -221,6 +221,7 @@ class SetStatusDialogFragment :
         )
     }
 
+    @Suppress("ComplexMethod")
     private fun setClearStatusAfterValue(item: Int) {
         when (item) {
             POS_DONT_CLEAR -> {

+ 24 - 0
src/main/java/com/owncloud/android/datamodel/QuickPermissionModel.kt

@@ -0,0 +1,24 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author TSI-mc
+ * Copyright (C) 2021 TSI-mc
+ * Copyright (C) 2021 Nextcloud GmbH
+ *
+ * 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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 <https://www.gnu.org/licenses/>.
+ */
+
+package com.owncloud.android.datamodel
+
+data class QuickPermissionModel(val permissionName : String, val isSelected : Boolean)

+ 64 - 4
src/main/java/com/owncloud/android/operations/CreateShareWithShareeOperation.java

@@ -3,7 +3,9 @@
  *
  *   @author masensio
  *   @author David A. Velasco
+ *   @author TSI-mc
  *   Copyright (C) 2015 ownCloud Inc.
+ *   Copyright (C) 2021 TSI-mc
  *
  *   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,6 +23,8 @@
 
 package com.owncloud.android.operations;
 
+import android.text.TextUtils;
+
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.lib.common.OwnCloudClient;
@@ -44,6 +48,11 @@ public class CreateShareWithShareeOperation extends SyncOperation {
     private String shareeName;
     private ShareType shareType;
     private int permissions;
+    private String noteMessage;
+    private String sharePassword;
+    private boolean hideFileDownload;
+    private long expirationDateInMillis;
+    private String label;
 
     private static final List<ShareType> supportedShareTypes = new ArrayList<>(Arrays.asList(ShareType.USER,
                                                                                              ShareType.GROUP,
@@ -78,6 +87,40 @@ public class CreateShareWithShareeOperation extends SyncOperation {
         this.permissions = permissions;
     }
 
+    /**
+     * Constructor.
+     *
+     * @param path        Full path of the file/folder being shared.
+     * @param shareeName  User or group name of the target sharee.
+     * @param shareType   Type of share determines type of sharee; {@link ShareType#USER} and {@link ShareType#GROUP}
+     *                    are the only valid values for the moment.
+     * @param permissions Share permissions key as detailed in https://doc.owncloud.org/server/8.2/developer_manual/core/ocs-share-api.html
+     *                    .
+     */
+    public CreateShareWithShareeOperation(String path,
+                                          String shareeName,
+                                          ShareType shareType,
+                                          int permissions,
+                                          String noteMessage,
+                                          String sharePassword,
+                                          long expirationDateInMillis,
+                                          boolean hideFileDownload,
+                                          FileDataStorageManager storageManager) {
+        super(storageManager);
+
+        if (!supportedShareTypes.contains(shareType)) {
+            throw new IllegalArgumentException("Illegal share type " + shareType);
+        }
+        this.path = path;
+        this.shareeName = shareeName;
+        this.shareType = shareType;
+        this.permissions = permissions;
+        this.expirationDateInMillis = expirationDateInMillis;
+        this.hideFileDownload = hideFileDownload;
+        this.noteMessage = noteMessage;
+        this.sharePassword = sharePassword;
+    }
+
     @Override
     protected RemoteOperationResult run(OwnCloudClient client) {
 
@@ -86,7 +129,7 @@ public class CreateShareWithShareeOperation extends SyncOperation {
             shareType,
             shareeName,
             false,
-            "",
+            sharePassword,
             permissions
         );
         operation.setGetShareDetails(true);
@@ -95,7 +138,20 @@ public class CreateShareWithShareeOperation extends SyncOperation {
 
         if (result.isSuccess() && result.getData().size() > 0) {
             OCShare share = (OCShare) result.getData().get(0);
-            updateData(share);
+
+            //once creating share link update other information
+            UpdateShareInfoOperation updateShareInfoOperation = new UpdateShareInfoOperation(share, getStorageManager());
+            updateShareInfoOperation.setExpirationDateInMillis(expirationDateInMillis);
+            updateShareInfoOperation.setHideFileDownload(hideFileDownload);
+            updateShareInfoOperation.setNote(noteMessage);
+            updateShareInfoOperation.setLabel(label);
+
+            //execute and save the result in database
+            RemoteOperationResult updateShareInfoResult = updateShareInfoOperation.execute(client);
+            if (updateShareInfoResult.isSuccess() && updateShareInfoResult.getData().size() > 0) {
+                OCShare shareUpdated = (OCShare) updateShareInfoResult.getData().get(0);
+                updateData(shareUpdated);
+            }
         }
 
         return result;
@@ -105,12 +161,12 @@ public class CreateShareWithShareeOperation extends SyncOperation {
         // Update DB with the response
         share.setPath(path);
         share.setFolder(path.endsWith(FileUtils.PATH_SEPARATOR));
-
+        share.setPasswordProtected(!TextUtils.isEmpty(sharePassword));
         getStorageManager().saveShare(share);
 
         // Update OCFile with data from share: ShareByLink  and publicLink
         OCFile file = getStorageManager().getFileByPath(path);
-        if (file!=null) {
+        if (file != null) {
             file.setSharedWithSharee(true);    // TODO - this should be done by the FileContentProvider, as part of getStorageManager().saveShare(share)
             getStorageManager().saveFile(file);
         }
@@ -119,4 +175,8 @@ public class CreateShareWithShareeOperation extends SyncOperation {
     public String getPath() {
         return this.path;
     }
+
+    public void setLabel(String label) {
+        this.label = label;
+    }
 }

+ 151 - 0
src/main/java/com/owncloud/android/operations/UpdateShareInfoOperation.java

@@ -0,0 +1,151 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author TSI-mc
+ * Copyright (C) 2021 TSI-mc
+ * Copyright (C) 2021 Nextcloud GmbH
+ *
+ * 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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 <https://www.gnu.org/licenses/>.
+ */
+
+package com.owncloud.android.operations;
+
+import android.text.TextUtils;
+
+import com.owncloud.android.datamodel.FileDataStorageManager;
+import com.owncloud.android.lib.common.OwnCloudClient;
+import com.owncloud.android.lib.common.operations.RemoteOperation;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+import com.owncloud.android.lib.resources.shares.GetShareRemoteOperation;
+import com.owncloud.android.lib.resources.shares.OCShare;
+import com.owncloud.android.lib.resources.shares.UpdateShareRemoteOperation;
+import com.owncloud.android.operations.common.SyncOperation;
+
+
+/**
+ * Updates an existing private share for a given file.
+ */
+public class UpdateShareInfoOperation extends SyncOperation {
+
+    private OCShare share;
+    private long shareId;
+    private long expirationDateInMillis;
+    private String note;
+    private boolean hideFileDownload;
+    private int permissions = -1;
+    private String password;
+    private String label;
+
+    /**
+     * Constructor
+     *
+     * @param share {@link OCShare} to update. Mandatory argument
+     *              <p>
+     *              this will be triggered while creating new share
+     */
+    public UpdateShareInfoOperation(OCShare share, FileDataStorageManager storageManager) {
+        super(storageManager);
+
+        this.share = share;
+        expirationDateInMillis = 0L;
+        note = null;
+    }
+
+    /**
+     * Constructor
+     *
+     * @param shareId {@link OCShare} to update. Mandatory argument
+     *                <p>
+     *                this will be triggered while modifying existing share
+     */
+    public UpdateShareInfoOperation(long shareId, FileDataStorageManager storageManager) {
+        super(storageManager);
+        
+        this.shareId = shareId;
+        expirationDateInMillis = 0L;
+        note = null;
+    }
+
+    @Override
+    protected RemoteOperationResult run(OwnCloudClient client) {
+
+        OCShare share;
+        if (shareId > 0) {
+            share = getStorageManager().getShareById(shareId);
+        } else {
+            share = this.share;
+        }
+
+        if (share == null) {
+            // TODO try to get remote share before failing?
+            return new RemoteOperationResult(RemoteOperationResult.ResultCode.SHARE_NOT_FOUND);
+        }
+
+        // Update remote share
+        UpdateShareRemoteOperation updateOp = new UpdateShareRemoteOperation(share.getRemoteId());
+        updateOp.setExpirationDate(expirationDateInMillis);
+        updateOp.setHideFileDownload(hideFileDownload);
+        if (!TextUtils.isEmpty(note)) {
+            updateOp.setNote(note);
+        }
+        if (permissions > -1) {
+            updateOp.setPermissions(permissions);
+        }
+        updateOp.setPassword(password);
+        updateOp.setLabel(label);
+
+        RemoteOperationResult result = updateOp.execute(client);
+
+        if (result.isSuccess()) {
+            RemoteOperation getShareOp = new GetShareRemoteOperation(share.getRemoteId());
+            result = getShareOp.execute(client);
+
+            //only update the share in storage if shareId is available
+            //this will be triggered by editing existing share
+            if (result.isSuccess() && shareId > 0) {
+                OCShare ocShare = (OCShare) result.getData().get(0);
+                ocShare.setPasswordProtected(!TextUtils.isEmpty(password));
+                getStorageManager().saveShare(ocShare);
+            }
+
+        }
+
+        return result;
+    }
+
+    public void setExpirationDateInMillis(long expirationDateInMillis) {
+        this.expirationDateInMillis = expirationDateInMillis;
+    }
+
+    public void setNote(String note) {
+        this.note = note;
+    }
+
+    public void setHideFileDownload(boolean hideFileDownload) {
+        this.hideFileDownload = hideFileDownload;
+    }
+
+    public void setPermissions(int permissions) {
+        this.permissions = permissions;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public void setLabel(String label) {
+        this.label = label;
+    }
+}
+

+ 103 - 57
src/main/java/com/owncloud/android/services/OperationsService.java

@@ -4,8 +4,10 @@
  *   @author masensio
  *   @author David A. Velasco
  *   @author Andy Scherzinger
+ *   @author TSI-mc
  *   Copyright (C) 2015 ownCloud Inc.
  *   Copyright (C) 2018 Andy Scherzinger
+ *   Copyright (C) 2021 TSI-mc
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -67,6 +69,7 @@ import com.owncloud.android.operations.SynchronizeFileOperation;
 import com.owncloud.android.operations.SynchronizeFolderOperation;
 import com.owncloud.android.operations.UnshareOperation;
 import com.owncloud.android.operations.UpdateNoteForShareOperation;
+import com.owncloud.android.operations.UpdateShareInfoOperation;
 import com.owncloud.android.operations.UpdateSharePermissionsOperation;
 import com.owncloud.android.operations.UpdateShareViaLinkOperation;
 
@@ -112,6 +115,7 @@ public class OperationsService extends Service {
     public static final String ACTION_UPDATE_PUBLIC_SHARE = "UPDATE_PUBLIC_SHARE";
     public static final String ACTION_UPDATE_USER_SHARE = "UPDATE_USER_SHARE";
     public static final String ACTION_UPDATE_SHARE_NOTE = "UPDATE_SHARE_NOTE";
+    public static final String ACTION_UPDATE_SHARE_INFO = "UPDATE_SHARE_INFO";
     public static final String ACTION_GET_SERVER_INFO = "GET_SERVER_INFO";
     public static final String ACTION_GET_USER_NAME = "GET_USER_NAME";
     public static final String ACTION_RENAME = "RENAME";
@@ -130,7 +134,7 @@ public class OperationsService extends Service {
     private SyncFolderHandler mSyncFolderHandler;
 
     private ConcurrentMap<Integer, Pair<RemoteOperation, RemoteOperationResult>>
-            mUndispatchedFinishedOperations = new ConcurrentHashMap<>();
+        mUndispatchedFinishedOperations = new ConcurrentHashMap<>();
 
     @Inject UserAccountManager accountManager;
 
@@ -155,7 +159,7 @@ public class OperationsService extends Service {
 
         // First worker thread for most of operations
         HandlerThread thread = new HandlerThread("Operations thread",
-                Process.THREAD_PRIORITY_BACKGROUND);
+                                                 Process.THREAD_PRIORITY_BACKGROUND);
         thread.start();
         mOperationsHandler = new ServiceHandler(thread.getLooper(), this);
         mOperationsBinder = new OperationsServiceBinder(mOperationsHandler);
@@ -169,8 +173,8 @@ public class OperationsService extends Service {
     /**
      * Entry point to add a new operation to the queue of operations.
      * <p/>
-     * New operations are added calling to startService(), resulting in a call to this method.
-     * This ensures the service will keep on working although the caller activity goes away.
+     * New operations are added calling to startService(), resulting in a call to this method. This ensures the service
+     * will keep on working although the caller activity goes away.
      */
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
@@ -191,8 +195,9 @@ public class OperationsService extends Service {
 
             Pair<Target, RemoteOperation> itemToQueue = newOperation(intent);
             if (itemToQueue != null) {
-                mSyncFolderHandler.add(account, remotePath,
-                        (SynchronizeFolderOperation)itemToQueue.second);
+                mSyncFolderHandler.add(account,
+                                       remotePath,
+                                       (SynchronizeFolderOperation) itemToQueue.second);
                 Message msg = mSyncFolderHandler.obtainMessage();
                 msg.arg1 = startId;
                 msg.obj = itemSyncKey;
@@ -210,7 +215,7 @@ public class OperationsService extends Service {
 
     @Override
     public void onDestroy() {
-        Log_OC.v(TAG, "Destroying service" );
+        Log_OC.v(TAG, "Destroying service");
         // Saving cookies
         OwnCloudClientManagerFactory.getDefaultSingleton()
             .saveAllClients(this, MainApp.getAccountType(getApplicationContext()));
@@ -229,8 +234,8 @@ public class OperationsService extends Service {
     }
 
     /**
-     * Provides a binder object that clients can use to perform actions on the queue of operations,
-     * except the addition of new operations.
+     * Provides a binder object that clients can use to perform actions on the queue of operations, except the addition
+     * of new operations.
      */
     @Override
     public IBinder onBind(Intent intent) {
@@ -256,8 +261,8 @@ public class OperationsService extends Service {
     public class OperationsServiceBinder extends Binder /* implements OnRemoteOperationListener */ {
 
         /**
-         * Map of listeners that will be reported about the end of operations from a
-         * {@link OperationsServiceBinder} instance
+         * Map of listeners that will be reported about the end of operations from a {@link OperationsServiceBinder}
+         * instance
          */
         private final ConcurrentMap<OnRemoteOperationListener, Handler> mBoundListeners = new ConcurrentHashMap<>();
 
@@ -271,8 +276,8 @@ public class OperationsService extends Service {
         /**
          * Cancels a pending or current synchronization.
          *
-         * @param account       ownCloud account where the remote folder is stored.
-         * @param file          A folder in the queue of pending synchronizations
+         * @param account ownCloud account where the remote folder is stored.
+         * @param file    A folder in the queue of pending synchronizations
          */
         public void cancel(Account account, OCFile file) {
             mSyncFolderHandler.cancel(account, file);
@@ -288,12 +293,11 @@ public class OperationsService extends Service {
         /**
          * Adds a listener interested in being reported about the end of operations.
          *
-         * @param listener          Object to notify about the end of operations.
-         * @param callbackHandler   {@link Handler} to access the listener without
-         *                                         breaking Android threading protection.
+         * @param listener        Object to notify about the end of operations.
+         * @param callbackHandler {@link Handler} to access the listener without breaking Android threading protection.
          */
-        public void addOperationListener (OnRemoteOperationListener listener,
-                                          Handler callbackHandler) {
+        public void addOperationListener(OnRemoteOperationListener listener,
+                                         Handler callbackHandler) {
             synchronized (mBoundListeners) {
                 mBoundListeners.put(listener, callbackHandler);
             }
@@ -301,10 +305,9 @@ public class OperationsService extends Service {
 
 
         /**
-         * Removes a listener from the list of objects interested in the being reported about
-         * the end of operations.
+         * Removes a listener from the list of objects interested in the being reported about the end of operations.
          *
-         * @param listener      Object to notify about progress of transfer.
+         * @param listener Object to notify about progress of transfer.
          */
         public void removeOperationListener(OnRemoteOperationListener listener) {
             synchronized (mBoundListeners) {
@@ -316,8 +319,7 @@ public class OperationsService extends Service {
         /**
          * TODO - IMPORTANT: update implementation when more operations are moved into the service
          *
-         * @return  'True' when an operation that enforces the user to wait for completion is
-         *          in process.
+         * @return 'True' when an operation that enforces the user to wait for completion is in process.
          */
         public boolean isPerformingBlockingOperation() {
             return !mServiceHandler.mPendingOperations.isEmpty();
@@ -326,11 +328,11 @@ public class OperationsService extends Service {
 
         /**
          * Creates and adds to the queue a new operation, as described by operationIntent.
-         *
+         * <p>
          * Calls startService to make the operation is processed by the ServiceHandler.
          *
-         * @param operationIntent       Intent describing a new operation to queue and execute.
-         * @return                      Identifier of the operation created, or null if failed.
+         * @param operationIntent Intent describing a new operation to queue and execute.
+         * @return Identifier of the operation created, or null if failed.
          */
         public long queueNewOperation(Intent operationIntent) {
             Pair<Target, RemoteOperation> itemToQueue = newOperation(operationIntent);
@@ -347,7 +349,7 @@ public class OperationsService extends Service {
         public boolean dispatchResultIfFinished(int operationId,
                                                 OnRemoteOperationListener listener) {
             Pair<RemoteOperation, RemoteOperationResult> undispatched =
-                    mUndispatchedFinishedOperations.remove(operationId);
+                mUndispatchedFinishedOperations.remove(operationId);
             if (undispatched != null) {
                 listener.onRemoteOperationFinish(undispatched.first, undispatched.second);
                 return true;
@@ -357,15 +359,14 @@ public class OperationsService extends Service {
         }
 
         /**
-         * Returns True when the file described by 'file' in the ownCloud account 'account' is
-         * downloading or waiting to download.
-         *
-         * If 'file' is a directory, returns 'true' if some of its descendant files is downloading
-         * or waiting to download.
+         * Returns True when the file described by 'file' in the ownCloud account 'account' is downloading or waiting to
+         * download.
+         * <p>
+         * If 'file' is a directory, returns 'true' if some of its descendant files is downloading or waiting to
+         * download.
          *
-         * @param user          user where the remote file is stored.
-         * @param file          File to check if something is synchronizing
-         *                      / downloading / uploading inside.
+         * @param user user where the remote file is stored.
+         * @param file File to check if something is synchronizing / downloading / uploading inside.
          */
         public boolean isSynchronizing(User user, OCFile file) {
             return mSyncFolderHandler.isSynchronizing(user, file.getRemotePath());
@@ -376,7 +377,7 @@ public class OperationsService extends Service {
 
     /**
      * Operations worker. Performs the pending operations in the order they were requested.
-     *
+     * <p>
      * Created with the Looper of a new thread, started in {@link OperationsService#onCreate()}.
      */
     private static class ServiceHandler extends Handler {
@@ -386,7 +387,7 @@ public class OperationsService extends Service {
 
 
         private ConcurrentLinkedQueue<Pair<Target, RemoteOperation>> mPendingOperations =
-                new ConcurrentLinkedQueue<>();
+            new ConcurrentLinkedQueue<>();
         private RemoteOperation mCurrentOperation;
         private Target mLastTarget;
         private OwnCloudClient mOwnCloudClient;
@@ -414,7 +415,7 @@ public class OperationsService extends Service {
             //Log_OC.e(TAG, "nextOperation init" );
 
             Pair<Target, RemoteOperation> next;
-            synchronized(mPendingOperations) {
+            synchronized (mPendingOperations) {
                 next = mPendingOperations.peek();
             }
 
@@ -440,20 +441,20 @@ public class OperationsService extends Service {
                 } catch (AccountsException e) {
                     if (mLastTarget.mAccount == null) {
                         Log_OC.e(TAG, "Error while trying to get authorization for a NULL account",
-                                e);
+                                 e);
                     } else {
                         Log_OC.e(TAG, "Error while trying to get authorization for " +
-                                mLastTarget.mAccount.name, e);
+                            mLastTarget.mAccount.name, e);
                     }
                     result = new RemoteOperationResult(e);
 
                 } catch (IOException e) {
                     if (mLastTarget.mAccount == null) {
                         Log_OC.e(TAG, "Error while trying to get authorization for a NULL account",
-                                e);
+                                 e);
                     } else {
                         Log_OC.e(TAG, "Error while trying to get authorization for " +
-                                mLastTarget.mAccount.name, e);
+                            mLastTarget.mAccount.name, e);
                     }
                     result = new RemoteOperationResult(e);
                 } catch (Exception e) {
@@ -465,7 +466,7 @@ public class OperationsService extends Service {
                     result = new RemoteOperationResult(e);
 
                 } finally {
-                    synchronized(mPendingOperations) {
+                    synchronized (mPendingOperations) {
                         mPendingOperations.poll();
                     }
                 }
@@ -479,19 +480,18 @@ public class OperationsService extends Service {
 
     /**
      * Creates a new operation, as described by operationIntent.
-     *
+     * <p>
      * TODO - move to ServiceHandler (probably)
      *
-     * @param operationIntent       Intent describing a new operation to queue and execute.
-     * @return                      Pair with the new operation object and the information about its
-     *                              target server.
+     * @param operationIntent Intent describing a new operation to queue and execute.
+     * @return Pair with the new operation object and the information about its target server.
      */
     private Pair<Target, RemoteOperation> newOperation(Intent operationIntent) {
         RemoteOperation operation = null;
         Target target = null;
         try {
             if (!operationIntent.hasExtra(EXTRA_ACCOUNT) &&
-                    !operationIntent.hasExtra(EXTRA_SERVER_URL)) {
+                !operationIntent.hasExtra(EXTRA_SERVER_URL)) {
                 Log_OC.e(TAG, "Not enough information provided in intent");
 
             } else {
@@ -583,12 +583,58 @@ public class OperationsService extends Service {
                         String shareeName = operationIntent.getStringExtra(EXTRA_SHARE_WITH);
                         shareType = (ShareType) operationIntent.getSerializableExtra(EXTRA_SHARE_TYPE);
                         int permissions = operationIntent.getIntExtra(EXTRA_SHARE_PERMISSIONS, -1);
+                        String noteMessage = operationIntent.getStringExtra(EXTRA_SHARE_NOTE);
+                        String sharePassword = operationIntent.getStringExtra(EXTRA_SHARE_PASSWORD);
+                        long expirationDateInMillis = operationIntent
+                            .getLongExtra(EXTRA_SHARE_EXPIRATION_DATE_IN_MILLIS, 0L);
+                        boolean hideFileDownload = operationIntent.getBooleanExtra(EXTRA_SHARE_HIDE_FILE_DOWNLOAD,
+                                                                                   false);
                         if (!TextUtils.isEmpty(remotePath)) {
-                            operation = new CreateShareWithShareeOperation(remotePath,
-                                                                           shareeName,
-                                                                           shareType,
-                                                                           permissions,
-                                                                           fileDataStorageManager);
+                            CreateShareWithShareeOperation createShareWithShareeOperation =
+                                new CreateShareWithShareeOperation(remotePath,
+                                                                   shareeName,
+                                                                   shareType,
+                                                                   permissions,
+                                                                   noteMessage,
+                                                                   sharePassword,
+                                                                   expirationDateInMillis,
+                                                                   hideFileDownload,
+                                                                   fileDataStorageManager);
+
+                            if (operationIntent.hasExtra(EXTRA_SHARE_PUBLIC_LABEL)) {
+                                createShareWithShareeOperation.setLabel(operationIntent.getStringExtra(EXTRA_SHARE_PUBLIC_LABEL));
+                            }
+                            operation = createShareWithShareeOperation;
+                        }
+                        break;
+
+                    case ACTION_UPDATE_SHARE_INFO:
+                        shareId = operationIntent.getLongExtra(EXTRA_SHARE_ID, -1);
+
+                        if (shareId > 0) {
+                            UpdateShareInfoOperation updateShare = new UpdateShareInfoOperation(shareId,
+                                                                                                fileDataStorageManager);
+
+                            int permissionsToChange = operationIntent.getIntExtra(EXTRA_SHARE_PERMISSIONS, -1);
+                            updateShare.setPermissions(permissionsToChange);
+
+                            long expirationDateInMills = operationIntent
+                                .getLongExtra(EXTRA_SHARE_EXPIRATION_DATE_IN_MILLIS, 0L);
+                            updateShare.setExpirationDateInMillis(expirationDateInMills);
+
+                            password = operationIntent.getStringExtra(EXTRA_SHARE_PASSWORD);
+                            updateShare.setPassword(password);
+
+                            boolean fileDownloadHide = operationIntent.getBooleanExtra(EXTRA_SHARE_HIDE_FILE_DOWNLOAD
+                                , false);
+
+                            updateShare.setHideFileDownload(fileDownloadHide);
+
+                            if (operationIntent.hasExtra(EXTRA_SHARE_PUBLIC_LABEL)) {
+                                updateShare.setLabel(operationIntent.getStringExtra(EXTRA_SHARE_PUBLIC_LABEL));
+                            }
+
+                            operation = updateShare;
                         }
                         break;
 
@@ -717,12 +763,12 @@ public class OperationsService extends Service {
     /**
      * Notifies the currently subscribed listeners about the end of an operation.
      *
-     * @param operation         Finished operation.
-     * @param result            Result of the operation.
+     * @param operation Finished operation.
+     * @param result    Result of the operation.
      */
     protected void dispatchResultToOperationListeners(
-            final RemoteOperation operation, final RemoteOperationResult result
-    ) {
+        final RemoteOperation operation, final RemoteOperationResult result
+                                                     ) {
         int count = 0;
         Iterator<OnRemoteOperationListener> listeners = mOperationsBinder.mBoundListeners.keySet().iterator();
         while (listeners.hasNext()) {

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

@@ -4,12 +4,14 @@
  * @author Andy Scherzinger
  * @author Tobias Kaminsky
  * @author Chris Narkiewicz  <hello@ezaquarii.com>
+ * @author TSI-mc
  * Copyright (C) 2016 Andy Scherzinger
  * Copyright (C) 2017 Tobias Kaminsky
  * Copyright (C) 2016 Nextcloud
  * Copyright (C) 2016 ownCloud Inc.
  * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
  * Copyright (C) 2020 Infomaniak Network SA
+ * Copyright (C) 2021 TSI-mc
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
@@ -98,6 +100,7 @@ import com.owncloud.android.ui.events.AccountRemovedEvent;
 import com.owncloud.android.ui.events.ChangeMenuEvent;
 import com.owncloud.android.ui.events.DummyDrawerEvent;
 import com.owncloud.android.ui.events.SearchEvent;
+import com.owncloud.android.ui.fragment.FileDetailsSharingProcessFragment;
 import com.owncloud.android.ui.fragment.GalleryFragment;
 import com.owncloud.android.ui.fragment.OCFileListFragment;
 import com.owncloud.android.ui.preview.PreviewTextStringFragment;
@@ -132,6 +135,7 @@ import androidx.core.content.ContextCompat;
 import androidx.core.content.res.ResourcesCompat;
 import androidx.core.view.GravityCompat;
 import androidx.drawerlayout.widget.DrawerLayout;
+import androidx.fragment.app.Fragment;
 
 /**
  * Base class to handle setup of the drawer implementation including user switching and avatar fetching and fallback
@@ -972,7 +976,13 @@ public abstract class DrawerActivity extends ToolbarActivity
             closeDrawer();
             return;
         }
-        super.onBackPressed();
+        Fragment fileDetailsSharingProcessFragment =
+            getSupportFragmentManager().findFragmentByTag(FileDetailsSharingProcessFragment.TAG);
+        if (fileDetailsSharingProcessFragment != null) {
+            ((FileDetailsSharingProcessFragment)fileDetailsSharingProcessFragment).onBackPressed();
+        } else {
+            super.onBackPressed();
+        }
     }
 
     @Override

+ 40 - 17
src/main/java/com/owncloud/android/ui/activity/FileActivity.java

@@ -3,9 +3,11 @@
  *
  *   @author David A. Velasco
  *   @author Chris Narkiewicz
+ *   @author TSI-mc
  *   Copyright (C) 2011  Bartek Przybylski
  *   Copyright (C) 2016 ownCloud Inc.
  *   Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
+ *   Copyright (C) 2021 TSI-mc
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -70,6 +72,7 @@ import com.owncloud.android.operations.SynchronizeFileOperation;
 import com.owncloud.android.operations.SynchronizeFolderOperation;
 import com.owncloud.android.operations.UnshareOperation;
 import com.owncloud.android.operations.UpdateNoteForShareOperation;
+import com.owncloud.android.operations.UpdateShareInfoOperation;
 import com.owncloud.android.operations.UpdateSharePermissionsOperation;
 import com.owncloud.android.operations.UpdateShareViaLinkOperation;
 import com.owncloud.android.providers.UsersAndGroupsSearchProvider;
@@ -112,7 +115,7 @@ import static com.owncloud.android.ui.activity.FileDisplayActivity.TAG_PUBLIC_LI
  */
 public abstract class FileActivity extends DrawerActivity
         implements OnRemoteOperationListener, ComponentsGetter, SslUntrustedCertDialog.OnSslUntrustedCertListener,
-        LoadingVersionNumberTask.VersionDevInterface {
+        LoadingVersionNumberTask.VersionDevInterface, FileDetailSharingFragment.OnEditShareListener {
 
     public static final String EXTRA_FILE = "com.owncloud.android.ui.activity.FILE";
     public static final String EXTRA_ACCOUNT = "com.owncloud.android.ui.activity.ACCOUNT";
@@ -406,7 +409,7 @@ public abstract class FileActivity extends DrawerActivity
             onCreateShareViaLinkOperationFinish((CreateShareViaLinkOperation) operation, result);
         } else if (operation instanceof CreateShareWithShareeOperation) {
             onUpdateShareInformation(result, R.string.sharee_add_failed);
-        } else if (operation instanceof UpdateShareViaLinkOperation) {
+        } else if (operation instanceof UpdateShareViaLinkOperation || operation instanceof UpdateShareInfoOperation) {
             onUpdateShareInformation(result, R.string.updating_share_failed);
         } else if (operation instanceof UpdateSharePermissionsOperation) {
             onUpdateShareInformation(result, R.string.updating_share_failed);
@@ -883,22 +886,42 @@ public abstract class FileActivity extends DrawerActivity
         }
     }
 
+    /**
+     * open the new sharing process fragment to create the share
+     * @param shareeName
+     * @param shareType
+     */
     private void doShareWith(String shareeName, ShareType shareType) {
-        getFileOperationsHelper().shareFileWithSharee(getFile(),
-                                                      shareeName,
-                                                      shareType,
-                                                      getAppropriatePermissions(shareType));
-    }
-
-    private int getAppropriatePermissions(ShareType shareType) {
-        if (getFile().isSharedWithMe()) {
-            return OCShare.READ_PERMISSION_FLAG;    // minimum permissions
-        } else if (ShareType.FEDERATED.equals(shareType)) {
-            return getFile().isFolder() ? OCShare.FEDERATED_PERMISSIONS_FOR_FOLDER :
-                OCShare.FEDERATED_PERMISSIONS_FOR_FILE;
-        } else {
-            return getFile().isFolder() ? OCShare.MAXIMUM_PERMISSIONS_FOR_FOLDER :
-                OCShare.MAXIMUM_PERMISSIONS_FOR_FILE;
+        Fragment fragment  = getSupportFragmentManager().findFragmentByTag(FileDisplayActivity.TAG_LIST_OF_FILES);
+        if (fragment!=null){
+            ((FileDetailFragment)fragment).initiateSharingProcess(shareeName, shareType);
+        }
+    }
+
+    /**
+     * open the new sharing process to modify the created share
+     * @param share
+     * @param screenTypePermission
+     * @param isReshareShown
+     * @param isExpiryDateShown
+     */
+    @Override
+    public void editExistingShare(OCShare share, int screenTypePermission, boolean isReshareShown,
+                                  boolean isExpiryDateShown) {
+        Fragment fragment  = getSupportFragmentManager().findFragmentByTag(FileDisplayActivity.TAG_LIST_OF_FILES);
+        if (fragment!=null){
+            ((FileDetailFragment)fragment).editExistingShare(share, screenTypePermission, isReshareShown, isExpiryDateShown);
+        }
+    }
+
+    /**
+     * callback triggered on closing/finishing the sharing process
+     */
+    @Override
+    public void onShareProcessClosed() {
+        Fragment fragment  = getSupportFragmentManager().findFragmentByTag(FileDisplayActivity.TAG_LIST_OF_FILES);
+        if (fragment!=null){
+            ((FileDetailFragment)fragment).showHideFragmentView(false);
         }
     }
 }

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

@@ -3,8 +3,11 @@
  * Nextcloud Android client application
  *
  * @author Tobias Kaminsky
+ * @author TSI-mc
+ *
  * Copyright (C) 2020 Tobias Kaminsky
  * Copyright (C) 2020 Nextcloud GmbH
+ * Copyright (C) 2021 TSI-mc
  *
  * 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
@@ -31,6 +34,7 @@ import com.owncloud.android.R;
 import com.owncloud.android.databinding.FileDetailsShareLinkShareItemBinding;
 import com.owncloud.android.lib.resources.shares.OCShare;
 import com.owncloud.android.lib.resources.shares.ShareType;
+import com.owncloud.android.ui.fragment.util.SharingMenuHelper;
 import com.owncloud.android.utils.theme.ThemeAvatarUtils;
 
 import androidx.annotation.NonNull;
@@ -74,7 +78,20 @@ class LinkShareViewHolder extends RecyclerView.ViewHolder {
             ThemeAvatarUtils.colorIconImageViewWithBackground(binding.icon, context);
         }
 
+        String permissionName = SharingMenuHelper.getPermissionName(context, publicShare);
+        setPermissionName(permissionName);
+
         binding.copyLink.setOnClickListener(v -> listener.copyLink(publicShare));
-        binding.overflowMenu.setOnClickListener(v -> listener.showLinkOverflowMenu(publicShare, binding.overflowMenu));
+        binding.overflowMenu.setOnClickListener(v -> listener.showSharingMenuActionSheet(publicShare));
+        binding.shareByLinkContainer.setOnClickListener(v -> listener.showPermissionsDialog(publicShare));
+    }
+
+    private void setPermissionName(String permissionName) {
+        if (!TextUtils.isEmpty(permissionName)) {
+            binding.permissionName.setText(permissionName);
+            binding.permissionName.setVisibility(View.VISIBLE);
+        } else {
+            binding.permissionName.setVisibility(View.GONE);
+        }
     }
 }

+ 81 - 0
src/main/java/com/owncloud/android/ui/adapter/QuickSharingPermissionsAdapter.kt

@@ -0,0 +1,81 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author TSI-mc
+ * Copyright (C) 2021 TSI-mc
+ * Copyright (C) 2021 Nextcloud GmbH
+ *
+ * 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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 <https://www.gnu.org/licenses/>.
+ */
+
+package com.owncloud.android.ui.adapter
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import com.owncloud.android.databinding.ItemQuickSharePermissionsBinding
+import com.owncloud.android.datamodel.QuickPermissionModel
+
+class QuickSharingPermissionsAdapter(private val quickPermissionList:
+MutableList<QuickPermissionModel>, private
+val onPermissionChangeListener: QuickSharingPermissionViewHolder.OnPermissionChangeListener) :
+    RecyclerView.Adapter<RecyclerView
+    .ViewHolder>() {
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+        val binding = ItemQuickSharePermissionsBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+        return QuickSharingPermissionViewHolder(binding, binding.root, onPermissionChangeListener)
+    }
+
+    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+        if (holder is QuickSharingPermissionViewHolder) {
+            holder.bindData(quickPermissionList[position])
+        }
+    }
+
+    override fun getItemCount(): Int {
+       return quickPermissionList.size
+    }
+
+    class QuickSharingPermissionViewHolder(val binding: ItemQuickSharePermissionsBinding,
+        itemView: View, val onPermissionChangeListener: OnPermissionChangeListener) :
+        RecyclerView
+        .ViewHolder(itemView) {
+
+        fun bindData(quickPermissionModel: QuickPermissionModel) {
+            binding.tvQuickShareName.text = quickPermissionModel.permissionName
+            if (quickPermissionModel.isSelected) {
+                binding.tvQuickShareCheckIcon.visibility = View.VISIBLE
+            } else {
+                binding.tvQuickShareCheckIcon.visibility = View.INVISIBLE
+            }
+
+            itemView.setOnClickListener {
+
+                //if user select different options then only update the permission
+                if (!quickPermissionModel.isSelected) {
+                   onPermissionChangeListener.onPermissionChanged(adapterPosition)
+                }else{
+                    //dismiss sheet on selection of same permission
+                    onPermissionChangeListener.onDismissSheet()
+                }
+            }
+        }
+
+        interface OnPermissionChangeListener{
+            fun onPermissionChanged(position: Int)
+            fun onDismissSheet()
+        }
+    }
+}

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

@@ -3,8 +3,10 @@
  * Nextcloud Android client application
  *
  * @author Tobias Kaminsky
+ * @author TSI-mc
  * Copyright (C) 2020 Tobias Kaminsky
  * Copyright (C) 2020 Nextcloud GmbH
+ * Copyright (C) 2021 TSI-mc
  *
  * 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 +25,8 @@
 package com.owncloud.android.ui.adapter;
 
 import android.content.Context;
+import android.text.TextUtils;
+import android.util.Log;
 import android.view.View;
 import android.widget.ImageView;
 
@@ -31,6 +35,7 @@ import com.owncloud.android.R;
 import com.owncloud.android.databinding.FileDetailsShareShareItemBinding;
 import com.owncloud.android.lib.resources.shares.OCShare;
 import com.owncloud.android.ui.TextDrawable;
+import com.owncloud.android.ui.fragment.util.SharingMenuHelper;
 import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.theme.ThemeAvatarUtils;
 
@@ -105,13 +110,26 @@ class ShareViewHolder extends RecyclerView.ViewHolder {
         if (share.getShareWith().equalsIgnoreCase(userId) || share.getUserId().equalsIgnoreCase(userId)) {
             binding.overflowMenu.setVisibility(View.VISIBLE);
 
+            String permissionName = SharingMenuHelper.getPermissionName(context, share);
+            setPermissionName(permissionName);
+
             // bind listener to edit privileges
-            binding.overflowMenu.setOnClickListener(v -> listener.showUserOverflowMenu(share, binding.overflowMenu));
+            binding.overflowMenu.setOnClickListener(v -> listener.showSharingMenuActionSheet(share));
+            binding.shareNameLayout.setOnClickListener(v -> listener.showPermissionsDialog(share));
         } else {
             binding.overflowMenu.setVisibility(View.GONE);
         }
     }
 
+    private void setPermissionName(String permissionName) {
+        if (!TextUtils.isEmpty(permissionName)) {
+            binding.permissionName.setText(permissionName);
+            binding.permissionName.setVisibility(View.VISIBLE);
+        } else {
+            binding.permissionName.setVisibility(View.GONE);
+        }
+    }
+
     private void setImage(ImageView avatar, String name, @DrawableRes int fallback) {
         try {
             avatar.setImageDrawable(TextDrawable.createNamedAvatar(name, avatarRadiusDimension));

+ 5 - 5
src/main/java/com/owncloud/android/ui/adapter/ShareeListAdapterListener.java

@@ -3,8 +3,10 @@
  * Nextcloud Android client application
  *
  * @author Tobias Kaminsky
+ * @author TSI-mc
  * Copyright (C) 2020 Tobias Kaminsky
  * Copyright (C) 2020 Nextcloud GmbH
+ * Copyright (C) 2021 TSI-mc
  *
  * 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
@@ -22,17 +24,13 @@
 
 package com.owncloud.android.ui.adapter;
 
-import android.widget.ImageView;
-
 import com.nextcloud.client.account.User;
 import com.owncloud.android.lib.resources.shares.OCShare;
 
 public interface ShareeListAdapterListener {
     void copyLink(OCShare share);
 
-    void showLinkOverflowMenu(OCShare publicShare, ImageView overflowMenuShareLink);
-
-    void showUserOverflowMenu(OCShare share, ImageView overflowMenu);
+    void showSharingMenuActionSheet(OCShare share);
 
     void copyInternalLink();
 
@@ -40,5 +38,7 @@ public interface ShareeListAdapterListener {
 
     void requestPasswordForShare(OCShare share, boolean askForPassword);
 
+    void showPermissionsDialog(OCShare share);
+
     void showProfileBottomSheet(User user, String shareWith);
 }

+ 23 - 19
src/main/java/com/owncloud/android/ui/dialog/ExpirationDatePickerDialogFragment.java

@@ -3,8 +3,10 @@
  *
  *   @author David A. Velasco
  *   @author Andy Scherzinger
+ *   @author TSI-mc
  *   Copyright (C) 2015 ownCloud Inc.
  *   Copyright (C) 2018 Andy Scherzinger
+ *   Copyright (C) 2018 TSI-mc
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -29,9 +31,6 @@ import android.text.format.DateUtils;
 import android.widget.DatePicker;
 
 import com.owncloud.android.R;
-import com.owncloud.android.lib.resources.shares.OCShare;
-import com.owncloud.android.ui.activity.FileActivity;
-import com.owncloud.android.ui.helpers.FileOperationsHelper;
 import com.owncloud.android.utils.theme.ThemeColorUtils;
 
 import java.util.Calendar;
@@ -49,25 +48,19 @@ public class ExpirationDatePickerDialogFragment
     /** Tag for FragmentsManager */
     public static final String DATE_PICKER_DIALOG = "DATE_PICKER_DIALOG";
 
-    /** 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";
 
-    /** Share to bind an expiration date */
-    private OCShare share;
+    private OnExpiryDateListener onExpiryDateListener;
 
     /**
      * 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(@NonNull OCShare share, long chosenDateInMillis) {
+    public static ExpirationDatePickerDialogFragment newInstance(long chosenDateInMillis) {
         Bundle arguments = new Bundle();
-        arguments.putParcelable(ARG_SHARE, share);
         arguments.putLong(ARG_CHOSEN_DATE_IN_MILLIS, chosenDateInMillis);
 
         ExpirationDatePickerDialogFragment dialog = new ExpirationDatePickerDialogFragment();
@@ -75,6 +68,10 @@ public class ExpirationDatePickerDialogFragment
         return dialog;
     }
 
+    public void setOnExpiryDateListener(OnExpiryDateListener onExpiryDateListener) {
+        this.onExpiryDateListener = onExpiryDateListener;
+    }
+
     /**
      * {@inheritDoc}
      *
@@ -83,8 +80,6 @@ public class ExpirationDatePickerDialogFragment
     @Override
     @NonNull
     public Dialog onCreateDialog(Bundle savedInstanceState) {
-        // Load arguments
-        share = requireArguments().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();
@@ -101,14 +96,18 @@ public class ExpirationDatePickerDialogFragment
             chosenDate.get(Calendar.MONTH),
             chosenDate.get(Calendar.DAY_OF_MONTH)
         );
-        dialog.setButton(
+
+        //show unset button only when date is already selected
+        if (chosenDateInMillis > 0) {
+            dialog.setButton(
                 Dialog.BUTTON_NEUTRAL,
                 getText(R.string.share_via_link_unset_password),
                 (dialog1, which) -> {
-                    if (share != null) {
-                        ((FileActivity) requireActivity()).getFileOperationsHelper().setExpirationDateToShare(share, -1);
+                    if (onExpiryDateListener != null) {
+                        onExpiryDateListener.onDateUnSet();
                     }
                 });
+        }
 
         dialog.show();
         dialog.getButton(DatePickerDialog.BUTTON_NEUTRAL).setTextColor(ThemeColorUtils.primaryColor(getContext(), true));
@@ -144,9 +143,14 @@ public class ExpirationDatePickerDialogFragment
         chosenDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
         long chosenDateInMillis = chosenDate.getTimeInMillis();
 
-        FileOperationsHelper operationsHelper = ((FileActivity) requireActivity()).getFileOperationsHelper();
-        if (share != null) {
-            operationsHelper.setExpirationDateToShare(share, chosenDateInMillis);
+        if (onExpiryDateListener != null) {
+            onExpiryDateListener.onDateSet(year, monthOfYear, dayOfMonth, chosenDateInMillis);
         }
     }
+
+    public interface OnExpiryDateListener {
+        void onDateSet(int year, int monthOfYear, int dayOfMonth, long chosenDateInMillis);
+
+        void onDateUnSet();
+    }
 }

+ 54 - 0
src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java

@@ -5,11 +5,13 @@
  *   @author David A. Velasco
  *   @author Andy Scherzinger
  *   @author Chris Narkiewicz
+ *   @author TSI-mc
  *
  *   Copyright (C) 2011 Bartek Przybylski
  *   Copyright (C) 2016 ownCloud Inc.
  *   Copyright (C) 2018 Andy Scherzinger
  *   Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
+ *   Copyright (C) 2021 TSI-mc
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -37,6 +39,7 @@ import android.view.ViewGroup;
 import android.widget.PopupMenu;
 import android.widget.ProgressBar;
 
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
 import com.google.android.material.tabs.TabLayout;
 import com.nextcloud.client.account.User;
 import com.nextcloud.client.account.UserAccountManager;
@@ -54,6 +57,8 @@ import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
 import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
 import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
 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.ui.activity.FileDisplayActivity;
 import com.owncloud.android.ui.activity.ToolbarActivity;
 import com.owncloud.android.ui.adapter.FileDetailTabAdapter;
@@ -665,6 +670,55 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
         binding.emptyList.emptyListIcon.setVisibility(View.VISIBLE);
     }
 
+    /**
+     * open the sharing process fragment for creating new share
+     * @param shareeName
+     * @param shareType
+     */
+    public void initiateSharingProcess(String shareeName, ShareType shareType) {
+        requireActivity().getSupportFragmentManager().beginTransaction().add(R.id.sharing_frame_container,
+                                                                             FileDetailsSharingProcessFragment.newInstance(getFile(),
+                                                                                                                           shareeName,
+                                                                                                                           shareType),
+                                                                             FileDetailsSharingProcessFragment.TAG)
+            .commit();
+
+        showHideFragmentView(true);
+    }
+
+    /**
+     * method will handle the views need to be hidden when sharing process fragment shows
+     * @param isFragmentReplaced
+     */
+    public void showHideFragmentView(boolean isFragmentReplaced) {
+        binding.tabLayout.setVisibility(isFragmentReplaced ? View.GONE : View.VISIBLE);
+        binding.pager.setVisibility(isFragmentReplaced ? View.GONE : View.VISIBLE);
+        binding.sharingFrameContainer.setVisibility(isFragmentReplaced ? View.VISIBLE : View.GONE);
+        FloatingActionButton mFabMain = requireActivity().findViewById(R.id.fab_main);
+        if (isFragmentReplaced) {
+            mFabMain.hide();
+        } else {
+            mFabMain.show();
+        }
+    }
+
+    /**
+     * open the new sharing screen process to modify the created share
+     * @param share
+     * @param screenTypePermission
+     * @param isReshareShown
+     * @param isExpiryDateShown
+     */
+    public void editExistingShare(OCShare share, int screenTypePermission, boolean isReshareShown,
+                                  boolean isExpiryDateShown) {
+        requireActivity().getSupportFragmentManager().beginTransaction().add(R.id.sharing_frame_container,
+                                                                             FileDetailsSharingProcessFragment.newInstance(share, screenTypePermission, isReshareShown,
+                                                                                                                           isExpiryDateShown),
+                                                                             FileDetailsSharingProcessFragment.TAG)
+            .commit();
+        showHideFragmentView(true);
+    }
+
     /**
      * Helper class responsible for updating the progress bar shown for file downloading.
      */

+ 81 - 286
src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java

@@ -3,9 +3,11 @@
  *
  * @author Andy Scherzinger
  * @author Chris Narkiewicz <hello@ezaquarii.com>
+ * @author TSI-mc
  *
  * Copyright (C) 2018 Andy Scherzinger
  * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
+ * Copyright (C) 2020 TSI-mc
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
@@ -24,19 +26,16 @@
 package com.owncloud.android.ui.fragment;
 
 import android.accounts.AccountManager;
+import android.app.AlertDialog;
 import android.app.SearchManager;
 import android.content.Context;
-import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.text.InputType;
 import android.text.TextUtils;
 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 com.nextcloud.client.account.User;
 import com.nextcloud.client.account.UserAccountManager;
@@ -49,7 +48,6 @@ import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.lib.common.OwnCloudAccount;
 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.NextcloudVersion;
 import com.owncloud.android.lib.resources.status.OCCapability;
@@ -59,17 +57,12 @@ import com.owncloud.android.ui.activity.FileDisplayActivity;
 import com.owncloud.android.ui.adapter.ShareeListAdapter;
 import com.owncloud.android.ui.adapter.ShareeListAdapterListener;
 import com.owncloud.android.ui.asynctasks.RetrieveHoverCardAsyncTask;
-import com.owncloud.android.ui.dialog.ExpirationDatePickerDialogFragment;
-import com.owncloud.android.ui.dialog.NoteDialogFragment;
-import com.owncloud.android.ui.dialog.RenamePublicShareDialogFragment;
 import com.owncloud.android.ui.dialog.SharePasswordDialogFragment;
 import com.owncloud.android.ui.fragment.util.FileDetailSharingFragmentHelper;
-import com.owncloud.android.ui.fragment.util.SharingMenuHelper;
 import com.owncloud.android.ui.helpers.FileOperationsHelper;
 import com.owncloud.android.utils.ClipboardUtil;
 import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.theme.ThemeToolbarUtils;
-import com.owncloud.android.utils.theme.ThemeUtils;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -79,23 +72,13 @@ import javax.inject.Inject;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
-import androidx.appcompat.widget.PopupMenu;
 import androidx.appcompat.widget.SearchView;
 import androidx.fragment.app.Fragment;
 import androidx.recyclerview.widget.LinearLayoutManager;
 
-import static com.owncloud.android.lib.resources.shares.OCShare.CREATE_PERMISSION_FLAG;
-import static com.owncloud.android.lib.resources.shares.OCShare.DELETE_PERMISSION_FLAG;
-import static com.owncloud.android.lib.resources.shares.OCShare.MAXIMUM_PERMISSIONS_FOR_FILE;
-import static com.owncloud.android.lib.resources.shares.OCShare.MAXIMUM_PERMISSIONS_FOR_FOLDER;
-import static com.owncloud.android.lib.resources.shares.OCShare.NO_PERMISSION;
-import static com.owncloud.android.lib.resources.shares.OCShare.READ_PERMISSION_FLAG;
-import static com.owncloud.android.lib.resources.shares.OCShare.SHARE_PERMISSION_FLAG;
-import static com.owncloud.android.lib.resources.shares.OCShare.UPDATE_PERMISSION_FLAG;
-
 public class FileDetailSharingFragment extends Fragment implements ShareeListAdapterListener,
     DisplayUtils.AvatarGenerationListener,
-    Injectable {
+    Injectable, FileDetailsSharingMenuBottomSheetActions, QuickSharingPermissionsBottomSheetDialog.QuickPermissionSharingBottomSheetActions {
 
     private static final String ARG_FILE = "FILE";
     private static final String ARG_USER = "USER";
@@ -111,6 +94,8 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda
 
     private FileDetailsSharingFragmentBinding binding;
 
+    private OnEditShareListener onEditShareListener;
+
     @Inject UserAccountManager accountManager;
 
     @Inject ClientFactory clientFactory;
@@ -198,6 +183,11 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda
         if (!(getActivity() instanceof FileActivity)) {
             throw new IllegalArgumentException("Calling activity must be of type FileActivity");
         }
+        try {
+            onEditShareListener = (OnEditShareListener) context;
+        } catch (Exception ignored) {
+            throw new IllegalArgumentException("Calling activity must implement the interface");
+        }
     }
 
     private void setupView() {
@@ -307,239 +297,24 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda
         }
     }
 
-    @Override
-    public void showUserOverflowMenu(OCShare share, ImageView overflowMenu) {
-        // use grey as fallback for elements where custom theming is not available
-        if (ThemeUtils.themingEnabled(requireContext())) {
-            requireContext().getTheme().applyStyle(R.style.FallbackThemingTheme, true);
-        }
-        PopupMenu popup = new PopupMenu(requireContext(), overflowMenu);
-        popup.inflate(R.menu.item_user_sharing_settings);
-        prepareUserOptionsMenu(popup.getMenu(), share);
-        popup.setOnMenuItemClickListener(item -> userOptionsItemSelected(popup.getMenu(), item, share));
-        popup.show();
-    }
-
     /**
-     * Updates the sharee's menu with the current permissions of the {@link OCShare}
+     * show share action bottom sheet
      *
-     * @param menu  the menu of the sharee/shared file
-     * @param share the shared file
+     * @param share
      */
+    @Override
     @VisibleForTesting
-    public void prepareUserOptionsMenu(Menu menu, OCShare share) {
-        MenuItem allowEditingItem = menu.findItem(R.id.allow_editing);
-        MenuItem allowCreatingItem = menu.findItem(R.id.allow_creating);
-        MenuItem allowDeletingItem = menu.findItem(R.id.allow_deleting);
-        MenuItem expirationDateItem = menu.findItem(R.id.action_expiration_date);
-        MenuItem reshareItem = menu.findItem(R.id.allow_resharing);
-
-        allowEditingItem.setChecked(canEdit(share));
-
-        if (isReshareForbidden(share)) {
-            reshareItem.setVisible(false);
-        }
-        reshareItem.setChecked(canReshare(share));
-
-        if (file.isFolder() || share.isFolder()) {
-            allowCreatingItem.setChecked(canCreate(share));
-            allowDeletingItem.setChecked(canDelete(share));
-        } else {
-            allowCreatingItem.setVisible(false);
-            allowDeletingItem.setVisible(false);
-        }
-
-        if (!capabilities.getVersion().isNewerOrEqual(OwnCloudVersion.nextcloud_18)) {
-            expirationDateItem.setVisible(false);
-        }
-
-        SharingMenuHelper.setupExpirationDateMenuItem(menu.findItem(R.id.action_expiration_date),
-                                                      share.getExpirationDate(),
-                                                      getResources());
-    }
-
-    public void showLinkOverflowMenu(OCShare publicShare, ImageView overflowMenuShareLink) {
-        if (ThemeUtils.themingEnabled(requireContext())) {
-            // use grey as fallback for elements where custom theming is not available
-            requireContext().getTheme().applyStyle(R.style.FallbackThemingTheme, true);
-        }
-
-        PopupMenu popup = new PopupMenu(requireContext(), overflowMenuShareLink);
-        if (ShareType.EMAIL == publicShare.getShareType()) {
-            popup.inflate(R.menu.fragment_file_detail_sharing_email_link);
-        } else {
-            popup.inflate(R.menu.fragment_file_detail_sharing_public_link);
-        }
-        prepareLinkOptionsMenu(popup.getMenu(), publicShare);
-        popup.setOnMenuItemClickListener(menuItem -> linkOptionsItemSelected(menuItem, publicShare));
-        popup.show();
-    }
-
-    @VisibleForTesting
-    public void prepareLinkOptionsMenu(Menu menu, OCShare publicShare) {
-        if (publicShare.isFolder()) {
-            menu.setGroupVisible(R.id.folder_permission, true);
-            menu.findItem(R.id.allow_editing).setVisible(false);
-
-            // read only / allow upload and editing / file drop
-            if (isUploadAndEditingAllowed(publicShare)) {
-                menu.findItem(R.id.link_share_allow_upload_and_editing).setChecked(true);
-            } else if (isFileDrop(publicShare)) {
-                menu.findItem(R.id.link_share_file_drop).setChecked(true);
-            } else if (isReadOnly(publicShare)) {
-                menu.findItem(R.id.link_share_read_only).setChecked(true);
-            }
-        } else {
-            menu.setGroupVisible(R.id.folder_permission, false);
-            menu.findItem(R.id.allow_editing).setVisible(true);
-
-            if (publicShare.getPermissions() > PERMISSION_EDITING_ALLOWED) {
-                menu.findItem(R.id.allow_editing).setChecked(true);
-            } else {
-                menu.findItem(R.id.allow_editing).setChecked(false);
-            }
-        }
-
-        Resources res = requireContext().getResources();
-        SharingMenuHelper.setupHideFileDownload(menu.findItem(R.id.action_hide_file_download),
-                                                publicShare.isHideFileDownload(),
-                                                isFileDrop(publicShare));
-
-        SharingMenuHelper.setupPasswordMenuItem(menu.findItem(R.id.action_password),
-                                                publicShare.isPasswordProtected());
-
-        SharingMenuHelper.setupExpirationDateMenuItem(menu.findItem(R.id.action_share_expiration_date),
-                                                      publicShare.getExpirationDate(),
-                                                      res);
-    }
-
-    @VisibleForTesting
-    public boolean isUploadAndEditingAllowed(OCShare share) {
-        if (share.getPermissions() == NO_PERMISSION) {
-            return false;
-        }
-
-        return (share.getPermissions() & MAXIMUM_PERMISSIONS_FOR_FOLDER) == MAXIMUM_PERMISSIONS_FOR_FOLDER;
-    }
-
-    @VisibleForTesting
-    public boolean isReadOnly(OCShare share) {
-        if (share.getPermissions() == NO_PERMISSION) {
-            return false;
-        }
-
-        return (share.getPermissions() & ~SHARE_PERMISSION_FLAG) == READ_PERMISSION_FLAG;
+    public void showSharingMenuActionSheet(OCShare share) {
+        new FileDetailSharingMenuBottomSheetDialog(fileActivity, this, share).show();
     }
 
-    @VisibleForTesting
-    public boolean isFileDrop(OCShare share) {
-        if (share.getPermissions() == NO_PERMISSION) {
-            return false;
-        }
-
-        return (share.getPermissions() & ~SHARE_PERMISSION_FLAG) == CREATE_PERMISSION_FLAG;
-    }
-
-    private boolean userOptionsItemSelected(Menu menu, MenuItem item, OCShare share) {
-        int itemId = item.getItemId();
-
-        if (itemId == R.id.allow_editing || itemId == R.id.allow_creating || itemId == R.id.allow_deleting || itemId == R.id.allow_resharing) {
-            item.setChecked(!item.isChecked());
-            share.setPermissions(updatePermissionsToShare(share,
-                                                          menu.findItem(R.id.allow_resharing).isChecked(),
-                                                          menu.findItem(R.id.allow_editing).isChecked(),
-                                                          menu.findItem(R.id.allow_creating).isChecked(),
-                                                          menu.findItem(R.id.allow_deleting).isChecked()));
-            return true;
-        } else if (itemId == R.id.action_unshare) {
-            unshareWith(share);
-            ShareeListAdapter adapter = (ShareeListAdapter) binding.sharesList.getAdapter();
-            if (adapter == null) {
-                DisplayUtils.showSnackMessage(getView(), getString(R.string.failed_update_ui));
-                return true;
-            }
-            adapter.remove(share);
-
-            return true;
-        } else if (itemId == R.id.action_expiration_date) {
-            ExpirationDatePickerDialogFragment dialog = ExpirationDatePickerDialogFragment
-                .newInstance(share, share.getExpirationDate());
-            dialog.show(fileActivity.getSupportFragmentManager(),
-                        ExpirationDatePickerDialogFragment.DATE_PICKER_DIALOG);
-            return true;
-        } else if (itemId == R.id.action_share_send_note) {
-            NoteDialogFragment dialog = NoteDialogFragment.newInstance(share);
-            dialog.show(fileActivity.getSupportFragmentManager(), NoteDialogFragment.NOTE_FRAGMENT);
-            return true;
-        }
-
-        return true;
-    }
-
-    public boolean linkOptionsItemSelected(MenuItem item, OCShare publicShare) {
-        int itemId = item.getItemId();
-
-        if (itemId == R.id.link_share_read_only) {
-            item.setChecked(true);
-            fileOperationsHelper.setPermissionsToShare(publicShare, READ_PERMISSION_FLAG);
-            return true;
-        } else if (itemId == R.id.link_share_allow_upload_and_editing) {
-            item.setChecked(true);
-            if (publicShare.isFolder()) {
-                fileOperationsHelper.setPermissionsToShare(publicShare, MAXIMUM_PERMISSIONS_FOR_FOLDER);
-            } else {
-                fileOperationsHelper.setPermissionsToShare(publicShare, MAXIMUM_PERMISSIONS_FOR_FILE);
-            }
-            return true;
-        } else if (itemId == R.id.link_share_file_drop) {
-            item.setChecked(true);
-            fileOperationsHelper.setPermissionsToShare(publicShare, CREATE_PERMISSION_FLAG);
-            return true;
-        } else if (itemId == R.id.allow_editing) {
-            if (file.isSharedViaLink()) {
-                item.setChecked(!item.isChecked());
-                fileOperationsHelper.setUploadPermissionsToPublicShare(publicShare, item.isChecked());
-            }
-            return true;
-        } else if (itemId == R.id.action_hide_file_download) {
-            item.setChecked(!item.isChecked());
-            fileOperationsHelper.setHideFileDownloadPermissionsToPublicShare(publicShare, item.isChecked());
-            return true;
-        } else if (itemId == R.id.action_password) {
-            requestPasswordForShare(publicShare,
-                                    capabilities.getFilesSharingPublicAskForOptionalPassword().isTrue());
-            return true;
-        } else if (itemId == R.id.action_share_expiration_date) {
-            ExpirationDatePickerDialogFragment expirationDialog = ExpirationDatePickerDialogFragment
-                .newInstance(publicShare, publicShare.getExpirationDate());
-            expirationDialog.show(fileActivity.getSupportFragmentManager(),
-                                  ExpirationDatePickerDialogFragment.DATE_PICKER_DIALOG);
-            return true;
-        } else if (itemId == R.id.action_share_send_link) {
-            if (file.isSharedViaLink() && !TextUtils.isEmpty(publicShare.getShareLink())) {
-                FileDisplayActivity.showShareLinkDialog(fileActivity, file, publicShare.getShareLink());
-            } else {
-                showSendLinkTo(publicShare);
-            }
-            return true;
-        } else if (itemId == R.id.action_share_send_note) {
-            NoteDialogFragment noteDialog = NoteDialogFragment.newInstance(publicShare);
-            noteDialog.show(fileActivity.getSupportFragmentManager(), NoteDialogFragment.NOTE_FRAGMENT);
-            return true;
-        } else if (itemId == R.id.action_edit_label) {
-            RenamePublicShareDialogFragment renameDialog = RenamePublicShareDialogFragment.newInstance(publicShare);
-            renameDialog.show(fileActivity.getSupportFragmentManager(),
-                              RenamePublicShareDialogFragment.RENAME_PUBLIC_SHARE_FRAGMENT);
-            return true;
-        } else if (itemId == R.id.action_unshare) {
-            fileOperationsHelper.unshareShare(file, publicShare);
-            return true;
-        } else if (itemId == R.id.action_add_another_public_share_link) {
-            createPublicShareLink();
-            return true;
-        }
-
-        return super.onOptionsItemSelected(item);
+    /**
+     * show quick sharing permission dialog
+     * @param share
+     */
+    @Override
+    public void showPermissionsDialog(OCShare share) {
+        new QuickSharingPermissionsBottomSheetDialog(fileActivity, this, share).show();
     }
 
     /**
@@ -571,27 +346,6 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda
         fileOperationsHelper.unshareShare(file, share);
     }
 
-    private int updatePermissionsToShare(OCShare share,
-                                         boolean canReshare,
-                                         boolean canEdit,
-                                         boolean canEditCreate,
-                                         boolean canEditDelete) {
-        SharePermissionsBuilder spb = new SharePermissionsBuilder();
-        spb.setSharePermission(canReshare);
-
-        if (file.isFolder()) {
-            spb.setCreatePermission(canEditCreate)
-                .setDeletePermission(canEditDelete);
-        } else {
-            spb.setUpdatePermission(canEdit);
-        }
-        int permissions = spb.build();
-
-        fileOperationsHelper.setPermissionsToShare(share, permissions);
-
-        return permissions;
-    }
-
     /**
      * Starts a dialog that requests a password to the user to protect a share link.
      *
@@ -627,8 +381,8 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda
     }
 
     /**
-     * Get public link from the DB to fill in the "Share link" section in the UI.
-     * Takes into account server capabilities before reading database.
+     * Get public link from the DB to fill in the "Share link" section in the UI. Takes into account server capabilities
+     * before reading database.
      */
     public void refreshSharesFromDB() {
         ShareeListAdapter adapter = (ShareeListAdapter) binding.sharesList.getAdapter();
@@ -698,30 +452,71 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda
             capabilities != null && capabilities.getFilesSharingResharing().isFalse();
     }
 
-    private boolean canEdit(OCShare share) {
-        return (share.getPermissions() &
-            (CREATE_PERMISSION_FLAG | UPDATE_PERMISSION_FLAG | DELETE_PERMISSION_FLAG)) > 0;
+    @VisibleForTesting
+    public void search(String query) {
+        SearchView searchView = getView().findViewById(R.id.searchView);
+        searchView.setQuery(query, true);
     }
 
-    private boolean canCreate(OCShare share) {
-        return (share.getPermissions() & CREATE_PERMISSION_FLAG) > 0;
+    public OCFile getFile() {
+        return file;
     }
 
-    private boolean canDelete(OCShare share) {
-        return (share.getPermissions() & DELETE_PERMISSION_FLAG) > 0;
+    @Override
+    public void openIn(OCShare share) {
+        fileOperationsHelper.sendShareFile(file);
     }
 
-    private boolean canReshare(OCShare share) {
-        return (share.getPermissions() & SHARE_PERMISSION_FLAG) > 0;
+    @Override
+    public void advancedPermissions(OCShare share) {
+        modifyExistingShare(share, FileDetailsSharingProcessFragment.SCREEN_TYPE_PERMISSION);
     }
 
-    @VisibleForTesting
-    public void search(String query) {
-        SearchView searchView = getView().findViewById(R.id.searchView);
-        searchView.setQuery(query, true);
+
+    @Override
+    public void sendNewEmail(OCShare share) {
+        modifyExistingShare(share, FileDetailsSharingProcessFragment.SCREEN_TYPE_NOTE);
     }
 
-    public OCFile getFile() {
-        return file;
+    @Override
+    public void unShare(OCShare share) {
+        unshareWith(share);
+        ShareeListAdapter adapter = (ShareeListAdapter) binding.sharesList.getAdapter();
+        if (adapter == null) {
+            DisplayUtils.showSnackMessage(getView(), getString(R.string.failed_update_ui));
+            return;
+        }
+        adapter.remove(share);
+    }
+
+    @Override
+    public void sendLink(OCShare share) {
+        if (file.isSharedViaLink() && !TextUtils.isEmpty(share.getShareLink())) {
+            FileDisplayActivity.showShareLinkDialog(fileActivity, file, share.getShareLink());
+        } else {
+            showSendLinkTo(share);
+        }
+    }
+
+    @Override
+    public void addAnotherLink(OCShare share) {
+        createPublicShareLink();
+    }
+
+    private void modifyExistingShare(OCShare share, int screenTypePermission) {
+        onEditShareListener.editExistingShare(share, screenTypePermission, !isReshareForbidden(share),
+                                              capabilities.getVersion().isNewerOrEqual(OwnCloudVersion.nextcloud_18));
+    }
+
+    @Override
+    public void onQuickPermissionChanged(OCShare share, int permission) {
+        fileOperationsHelper.setPermissionsToShare(share, permission);
+    }
+
+    public interface OnEditShareListener {
+        void editExistingShare(OCShare share, int screenTypePermission, boolean isReshareShown,
+                               boolean isExpiryDateShown);
+
+        void onShareProcessClosed();
     }
 }

+ 124 - 0
src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingMenuBottomSheetDialog.java

@@ -0,0 +1,124 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author TSI-mc
+ * Copyright (C) 2021 TSI-mc
+ * Copyright (C) 2021 Nextcloud GmbH
+ *
+ * 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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 <https://www.gnu.org/licenses/>.
+ */
+
+package com.owncloud.android.ui.fragment;
+
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.google.android.material.bottomsheet.BottomSheetBehavior;
+import com.google.android.material.bottomsheet.BottomSheetDialog;
+import com.owncloud.android.databinding.FileDetailsSharingMenuBottomSheetFragmentBinding;
+import com.owncloud.android.lib.resources.shares.OCShare;
+import com.owncloud.android.lib.resources.shares.ShareType;
+import com.owncloud.android.ui.activity.FileActivity;
+
+/**
+ * File Details Sharing option menus {@link android.app.Dialog} styled as a bottom sheet for main actions.
+ */
+public class FileDetailSharingMenuBottomSheetDialog extends BottomSheetDialog {
+    private FileDetailsSharingMenuBottomSheetFragmentBinding binding;
+    private final FileDetailsSharingMenuBottomSheetActions actions;
+    private final OCShare ocShare;
+
+    public FileDetailSharingMenuBottomSheetDialog(FileActivity fileActivity,
+                                                  FileDetailsSharingMenuBottomSheetActions actions,
+                                                  OCShare ocShare) {
+        super(fileActivity);
+        this.actions = actions;
+        this.ocShare = ocShare;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        binding = FileDetailsSharingMenuBottomSheetFragmentBinding.inflate(getLayoutInflater());
+        setContentView(binding.getRoot());
+
+        if (getWindow() != null) {
+            getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+        }
+
+        updateUI();
+
+        setupClickListener();
+
+        setOnShowListener(d ->
+                              BottomSheetBehavior.from((View) binding.getRoot().getParent())
+                                  .setPeekHeight(binding.getRoot().getMeasuredHeight())
+                         );
+    }
+
+    private void updateUI() {
+        if (ocShare.isFolder()) {
+            binding.menuShareOpenIn.setVisibility(View.GONE);
+        } else {
+            binding.menuShareOpenIn.setVisibility(View.VISIBLE);
+        }
+
+        if (ocShare.getShareType() == ShareType.PUBLIC_LINK) {
+            binding.menuShareAddAnotherLink.setVisibility(View.VISIBLE);
+            binding.menuShareSendLink.setVisibility(View.VISIBLE);
+        } else {
+            binding.menuShareAddAnotherLink.setVisibility(View.GONE);
+            binding.menuShareSendLink.setVisibility(View.GONE);
+        }
+    }
+
+    private void setupClickListener() {
+        binding.menuShareOpenIn.setOnClickListener(v -> {
+            actions.openIn(ocShare);
+            dismiss();
+        });
+
+        binding.menuShareAdvancedPermissions.setOnClickListener(v -> {
+            actions.advancedPermissions(ocShare);
+            dismiss();
+        });
+
+        binding.menuShareSendNewEmail.setOnClickListener(v -> {
+            actions.sendNewEmail(ocShare);
+            dismiss();
+        });
+
+        binding.menuShareUnshare.setOnClickListener(v -> {
+            actions.unShare(ocShare);
+            dismiss();
+        });
+
+        binding.menuShareSendLink.setOnClickListener(v -> {
+            actions.sendLink(ocShare);
+            dismiss();
+        });
+
+        binding.menuShareAddAnotherLink.setOnClickListener(v -> {
+            actions.addAnotherLink(ocShare);
+            dismiss();
+        });
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        binding = null;
+    }
+}

+ 61 - 0
src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingMenuBottomSheetActions.java

@@ -0,0 +1,61 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author TSI-mc
+ * Copyright (C) 2021 TSI-mc
+ * Copyright (C) 2021 Nextcloud GmbH
+ *
+ * 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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 <https://www.gnu.org/licenses/>.
+ */
+
+package com.owncloud.android.ui.fragment;
+
+
+import com.owncloud.android.lib.resources.shares.OCShare;
+import com.owncloud.android.lib.resources.shares.ShareType;
+
+/**
+ * Actions interface to be implemented by any class that makes use of {@link FileDetailSharingMenuBottomSheetDialog}.
+ */
+public interface FileDetailsSharingMenuBottomSheetActions {
+    /**
+     * open sharing options only applicable for files
+     */
+    void openIn(OCShare share);
+
+    /**
+     * open advanced permission for selected share
+     */
+    void advancedPermissions(OCShare share);
+
+    /**
+     * open note screen to send new email
+     */
+    void sendNewEmail(OCShare share);
+
+    /**
+     * unshare the current share
+     */
+    void unShare(OCShare share);
+
+    /**
+     * send created link only valid for {@link ShareType#PUBLIC_LINK}
+     */
+    void sendLink(OCShare share);
+
+    /**
+     * create another link only valid for {@link ShareType#PUBLIC_LINK}
+     */
+    void addAnotherLink(OCShare share);
+}

+ 536 - 0
src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt

@@ -0,0 +1,536 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author TSI-mc
+ * Copyright (C) 2021 TSI-mc
+ * Copyright (C) 2021 Nextcloud GmbH
+ *
+ * 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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 <https://www.gnu.org/licenses/>.
+ */
+
+package com.owncloud.android.ui.fragment
+
+import android.content.Context
+import android.os.Bundle
+import android.text.TextUtils
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import com.owncloud.android.R
+import com.owncloud.android.databinding.FileDetailsSharingProcessFragmentBinding
+import com.owncloud.android.datamodel.OCFile
+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.ui.activity.FileActivity
+import com.owncloud.android.ui.dialog.ExpirationDatePickerDialogFragment
+import com.owncloud.android.ui.fragment.util.SharingMenuHelper
+import com.owncloud.android.ui.helpers.FileOperationsHelper
+import com.owncloud.android.utils.ClipboardUtil
+import com.owncloud.android.utils.DisplayUtils
+import java.text.SimpleDateFormat
+import java.util.Date
+
+/**
+ * Fragment class to show share permission options, set expiration date, change label, set password, send note
+ *
+ * This fragment handles following:
+ * 1. This will be shown while creating new internal and external share. So that user can set every share
+ * configuration at one time.
+ * 2. This will handle both Advanced Permissions and Send New Email functionality for existing shares to modify them.
+ */
+@Suppress("TooManyFunctions")
+class FileDetailsSharingProcessFragment : Fragment(), ExpirationDatePickerDialogFragment.OnExpiryDateListener {
+
+    companion object {
+        const val TAG = "FileDetailsSharingProcessFragment"
+        private const val ARG_OCFILE = "arg_sharing_oc_file"
+        private const val ARG_SHAREE_NAME = "arg_sharee_name"
+        private const val ARG_SHARE_TYPE = "arg_share_type"
+        private const val ARG_OCSHARE = "arg_ocshare"
+        private const val ARG_SCREEN_TYPE = "arg_screen_type"
+        private const val ARG_RESHARE_SHOWN = "arg_reshare_shown"
+        private const val ARG_EXP_DATE_SHOWN = "arg_exp_date_shown"
+
+        //types of screens to be displayed
+        const val SCREEN_TYPE_PERMISSION = 1 // permissions screen
+        const val SCREEN_TYPE_NOTE = 2 //note screen
+
+        /**
+         * fragment instance to be called while creating new share for internal and external share
+         */
+        @JvmStatic
+        fun newInstance(file: OCFile, shareeName: String, shareType: ShareType): FileDetailsSharingProcessFragment {
+            val args = Bundle()
+            args.putParcelable(ARG_OCFILE, file)
+            args.putSerializable(ARG_SHARE_TYPE, shareType)
+            args.putString(ARG_SHAREE_NAME, shareeName)
+            val fragment = FileDetailsSharingProcessFragment()
+            fragment.arguments = args
+            return fragment
+        }
+
+        /**
+         * fragment instance to be called while modifying existing share information
+         */
+        @JvmStatic
+        fun newInstance(share: OCShare, screenType: Int, isReshareShown: Boolean, isExpirationDateShown: Boolean):
+            FileDetailsSharingProcessFragment {
+            val args = Bundle()
+            args.putParcelable(ARG_OCSHARE, share)
+            args.putInt(ARG_SCREEN_TYPE, screenType)
+            args.putBoolean(ARG_RESHARE_SHOWN, isReshareShown)
+            args.putBoolean(ARG_EXP_DATE_SHOWN, isExpirationDateShown)
+            val fragment = FileDetailsSharingProcessFragment()
+            fragment.arguments = args
+            return fragment
+        }
+    }
+
+    private lateinit var onEditShareListener: FileDetailSharingFragment.OnEditShareListener
+
+    private lateinit var binding: FileDetailsSharingProcessFragmentBinding
+    private var fileOperationsHelper: FileOperationsHelper? = null
+    private var fileActivity: FileActivity? = null
+
+    private var file: OCFile? = null //file to be share
+    private var shareeName: String? = null
+    private lateinit var shareType: ShareType
+    private var shareProcessStep = SCREEN_TYPE_PERMISSION //default screen type
+    private var permission = OCShare.NO_PERMISSION //no permission
+    private var chosenExpDateInMills: Long = -1 //for no expiry date
+
+    private var share: OCShare? = null
+    private var isReshareShown: Boolean = true //show or hide reshare option
+    private var isExpDateShown: Boolean = true //show or hide expiray date option
+
+    override fun onAttach(context: Context) {
+        super.onAttach(context)
+        try {
+            onEditShareListener = context as FileDetailSharingFragment.OnEditShareListener
+        } catch (e: ClassCastException) {
+            throw IllegalStateException("Calling activity must implement the interface")
+        }
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        arguments?.let {
+            file = it.getParcelable(ARG_OCFILE)
+            shareeName = it.getString(ARG_SHAREE_NAME)
+            if (it.containsKey(ARG_SHARE_TYPE)) {
+                shareType = it.getSerializable(ARG_SHARE_TYPE) as ShareType
+            }
+            share = it.getParcelable(ARG_OCSHARE)
+            shareProcessStep = it.getInt(ARG_SCREEN_TYPE, SCREEN_TYPE_PERMISSION)
+            isReshareShown = it.getBoolean(ARG_RESHARE_SHOWN, true)
+            isExpDateShown = it.getBoolean(ARG_EXP_DATE_SHOWN, true)
+        }
+
+        fileActivity = activity as FileActivity?
+
+        requireNotNull(fileActivity) { "FileActivity may not be null" }
+    }
+
+    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
+        binding = FileDetailsSharingProcessFragmentBinding.inflate(inflater, container, false)
+        fileOperationsHelper = fileActivity?.fileOperationsHelper
+        return binding.root
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        if (shareProcessStep == SCREEN_TYPE_PERMISSION) {
+            showShareProcessFirst()
+        } else {
+            showShareProcessSecond()
+        }
+        implementClickEvents()
+    }
+
+    private fun showShareProcessFirst() {
+        binding.shareProcessGroupOne.visibility = View.VISIBLE
+        binding.shareProcessGroupTwo.visibility = View.GONE
+
+        //set up UI for modifying share
+        if (share != null) {
+            if (share?.isFolder == true) {
+                updateViewForFolder()
+            } else {
+                updateViewForFile()
+            }
+
+            // read only / allow upload and editing / file drop
+            if (SharingMenuHelper.isUploadAndEditingAllowed(share)) {
+                binding.shareProcessPermissionUploadEditing.isChecked = true
+            } else if (SharingMenuHelper.isFileDrop(share) && share?.isFolder == true) {
+                binding.shareProcessPermissionFileDrop.isChecked = true
+            } else if (SharingMenuHelper.isReadOnly(share)) {
+                binding.shareProcessPermissionReadOnly.isChecked = true
+            }
+
+            shareType = share?.shareType ?: ShareType.NO_SHARED
+            //show different text for link share and other shares
+            //because we have link to share in Public Link
+            if (shareType == ShareType.PUBLIC_LINK) {
+                binding.shareProcessBtnNext.text = requireContext().resources.getString(R.string.share_copy_link)
+            } else {
+                binding.shareProcessBtnNext.text = requireContext().resources.getString(R.string.common_confirm)
+            }
+            updateViewForShareType()
+            binding.shareProcessSetPasswordSwitch.isChecked = share?.isPasswordProtected == true
+            showPasswordInput(binding.shareProcessSetPasswordSwitch.isChecked)
+            updateExpirationDateView()
+            showExpirationDateInput(binding.shareProcessSetExpDateSwitch.isChecked)
+        }
+
+        //update UI for creating new share
+        else {
+            binding.shareProcessBtnNext.text = requireContext().resources.getString(R.string.common_next)
+            file.let {
+                if (file?.isFolder == true) {
+                    updateViewForFolder()
+                } else {
+                    updateViewForFile()
+                }
+                updateViewForShareType()
+            }
+            showPasswordInput(binding.shareProcessSetPasswordSwitch.isChecked)
+            showExpirationDateInput(binding.shareProcessSetExpDateSwitch.isChecked)
+        }
+
+        //show or hide expiry date
+        if (isExpDateShown) {
+            binding.shareProcessSetExpDateSwitch.visibility = View.VISIBLE
+        } else {
+            binding.shareProcessSetExpDateSwitch.visibility = View.GONE
+        }
+        shareProcessStep = SCREEN_TYPE_PERMISSION
+    }
+
+    /**
+     * method to update views on the basis of Share type
+     */
+    private fun updateViewForShareType() {
+        //external share
+        if (shareType == ShareType.EMAIL) {
+            binding.shareProcessChangeNameSwitch.visibility = View.GONE
+            binding.shareProcessChangeNameEt.visibility = View.GONE
+            updateViewForExternalAndLinkShare()
+        }
+        //link share
+        else if (shareType == ShareType.PUBLIC_LINK) {
+            updateViewForExternalAndLinkShare()
+            binding.shareProcessChangeNameSwitch.visibility = View.VISIBLE
+            if (share != null) {
+                binding.shareProcessChangeNameEt.setText(share?.label)
+                binding.shareProcessChangeNameSwitch.isChecked = !TextUtils.isEmpty(share?.label)
+            }
+            showChangeNameInput(binding.shareProcessChangeNameSwitch.isChecked)
+        }
+        //internal share
+        else {
+            binding.shareProcessChangeNameSwitch.visibility = View.GONE
+            binding.shareProcessChangeNameEt.visibility = View.GONE
+            binding.shareProcessHideDownloadCheckbox.visibility = View.GONE
+            binding.shareProcessAllowResharingCheckbox.visibility = View.VISIBLE
+            binding.shareProcessSetPasswordSwitch.visibility = View.GONE
+            if (share != null) {
+                if (!isReshareShown) {
+                    binding.shareProcessAllowResharingCheckbox.visibility = View.GONE
+                }
+                binding.shareProcessAllowResharingCheckbox.isChecked = SharingMenuHelper.canReshare(share)
+            }
+        }
+    }
+
+    /**
+     * update views where share type external or link share
+     */
+    private fun updateViewForExternalAndLinkShare() {
+        binding.shareProcessHideDownloadCheckbox.visibility = View.VISIBLE
+        binding.shareProcessAllowResharingCheckbox.visibility = View.GONE
+        binding.shareProcessSetPasswordSwitch.visibility = View.VISIBLE
+
+        if (share != null) {
+            if (SharingMenuHelper.isFileDrop(share)) {
+                binding.shareProcessHideDownloadCheckbox.visibility = View.GONE
+            } else {
+                binding.shareProcessHideDownloadCheckbox.visibility = View.VISIBLE
+                binding.shareProcessHideDownloadCheckbox.isChecked = share?.isHideFileDownload == true
+            }
+        }
+    }
+
+    /**
+     * update expiration date view while modifying the share
+     */
+    private fun updateExpirationDateView() {
+        if (share != null) {
+            if (share?.expirationDate ?: 0 > 0) {
+                chosenExpDateInMills = share?.expirationDate ?: -1
+                binding.shareProcessSetExpDateSwitch.isChecked = true
+                binding.shareProcessSelectExpDate.text = (
+                    resources.getString(
+                        R.string.share_expiration_date_format,
+                        SimpleDateFormat.getDateInstance().format(Date(share?.expirationDate ?: 0))
+                    )
+                    )
+            }
+        }
+    }
+
+    private fun updateViewForFile() {
+        binding.shareProcessPermissionUploadEditing.text =
+            requireContext().resources.getString(R.string.link_share_editing)
+        binding.shareProcessPermissionFileDrop.visibility = View.GONE
+    }
+
+    private fun updateViewForFolder() {
+        binding.shareProcessPermissionUploadEditing.text =
+            requireContext().resources.getString(R.string.link_share_allow_upload_and_editing)
+        binding.shareProcessPermissionFileDrop.visibility = View.VISIBLE
+    }
+
+    /**
+     * update views for screen type Note
+     */
+    private fun showShareProcessSecond() {
+        binding.shareProcessGroupOne.visibility = View.GONE
+        binding.shareProcessGroupTwo.visibility = View.VISIBLE
+        if (share != null) {
+            binding.shareProcessBtnNext.text = requireContext().resources.getString(R.string.set_note)
+            binding.noteText.setText(share?.note)
+        } else {
+            binding.shareProcessBtnNext.text = requireContext().resources.getString(R.string.send_share)
+            binding.noteText.setText("")
+        }
+        shareProcessStep = SCREEN_TYPE_NOTE
+    }
+
+    private fun implementClickEvents() {
+        binding.shareProcessBtnCancel.setOnClickListener {
+            onCancelClick()
+        }
+        binding.shareProcessBtnNext.setOnClickListener {
+            if (shareProcessStep == SCREEN_TYPE_PERMISSION) {
+                validateShareProcessFirst()
+            } else {
+                validateShareProcessSecond()
+            }
+        }
+        binding.shareProcessSetPasswordSwitch.setOnCheckedChangeListener { _, isChecked ->
+            showPasswordInput(isChecked)
+        }
+        binding.shareProcessSetExpDateSwitch.setOnCheckedChangeListener { _, isChecked ->
+            showExpirationDateInput(isChecked)
+        }
+        binding.shareProcessChangeNameSwitch.setOnCheckedChangeListener { _, isChecked ->
+            showChangeNameInput(isChecked)
+        }
+        binding.shareProcessSelectExpDate.setOnClickListener {
+            showExpirationDateDialog()
+        }
+    }
+
+    private fun showExpirationDateDialog() {
+        val dialog = ExpirationDatePickerDialogFragment.newInstance(chosenExpDateInMills)
+        dialog.setOnExpiryDateListener(this)
+        fileActivity?.let { it1 ->
+            dialog.show(
+                it1.supportFragmentManager,
+                ExpirationDatePickerDialogFragment.DATE_PICKER_DIALOG
+            )
+        }
+    }
+
+    private fun showChangeNameInput(isChecked: Boolean) {
+        binding.shareProcessChangeNameEt.visibility = if (isChecked) View.VISIBLE else View.GONE
+        if (!isChecked) {
+            binding.shareProcessChangeNameEt.setText("")
+        }
+    }
+
+    private fun onCancelClick() {
+        //if modifying the existing share then on back press remove the current fragment
+        if (share != null) {
+            removeCurrentFragment()
+        }
+        //else we have to check if user is in step 2(note screen) then show step 1 (permission screen)
+        //and if user is in step 1 (permission screen) then remove the fragment
+        else {
+            if (shareProcessStep == SCREEN_TYPE_NOTE) {
+                showShareProcessFirst()
+            } else {
+                removeCurrentFragment()
+            }
+        }
+    }
+
+    private fun showExpirationDateInput(isChecked: Boolean) {
+        binding.shareProcessSelectExpDate.visibility = if (isChecked) View.VISIBLE else View.GONE
+        binding.shareProcessExpDateDivider.visibility = if (isChecked) View.VISIBLE else View.GONE
+
+        //reset the expiration date if switch is unchecked
+        if (!isChecked) {
+            chosenExpDateInMills = -1
+            binding.shareProcessSelectExpDate.text = ""
+        }
+    }
+
+    private fun showPasswordInput(isChecked: Boolean) {
+        binding.shareProcessEnterPassword.visibility = if (isChecked) View.VISIBLE else View.GONE
+
+        //reset the password if switch is unchecked
+        if (!isChecked) {
+            binding.shareProcessEnterPassword.setText("")
+        }
+    }
+
+    private fun removeCurrentFragment() {
+        onEditShareListener.onShareProcessClosed()
+        fileActivity?.supportFragmentManager?.beginTransaction()?.remove(this)?.commit()
+    }
+
+    private fun getResharePermission(): Int {
+        val spb = SharePermissionsBuilder()
+        spb.setSharePermission(true)
+        return spb.build()
+    }
+
+    /**
+     * method to validate the step 1 screen information
+     */
+    @Suppress("ReturnCount")
+    private fun validateShareProcessFirst() {
+        //get the permissions on the basis of selection
+        when {
+            binding.shareProcessPermissionReadOnly.isChecked -> {
+                permission = OCShare.READ_PERMISSION_FLAG
+            }
+            binding.shareProcessPermissionUploadEditing.isChecked -> {
+                permission = if (file?.isFolder == true || share?.isFolder == true) {
+                    OCShare.MAXIMUM_PERMISSIONS_FOR_FOLDER
+                } else {
+                    OCShare.MAXIMUM_PERMISSIONS_FOR_FILE
+                }
+            }
+            binding.shareProcessPermissionFileDrop.isChecked -> {
+                permission = OCShare.CREATE_PERMISSION_FLAG
+            }
+        }
+
+        if (binding.shareProcessAllowResharingCheckbox.isChecked) {
+            permission = getResharePermission()
+        }
+
+        if (permission == OCShare.NO_PERMISSION) {
+            DisplayUtils.showSnackMessage(binding.root, R.string.no_share_permission_selected)
+            return
+        }
+
+        if (binding.shareProcessSetPasswordSwitch.isChecked && TextUtils.isEmpty(
+                binding.shareProcessEnterPassword
+                    .text.toString().trim()
+            )
+        ) {
+            DisplayUtils.showSnackMessage(binding.root, R.string.share_link_empty_password)
+            return
+        }
+
+        if (binding.shareProcessSetExpDateSwitch.isChecked && TextUtils.isEmpty(
+                binding.shareProcessSelectExpDate
+                    .text.toString().trim()
+            )
+        ) {
+            showExpirationDateDialog()
+            return
+        }
+
+        if (binding.shareProcessChangeNameSwitch.isChecked && TextUtils.isEmpty(
+                binding.shareProcessChangeNameEt
+                    .text.toString().trim()
+            )
+        ) {
+            DisplayUtils.showSnackMessage(binding.root, R.string.label_empty)
+            return
+        }
+
+        //if modifying existing share information then execute the process
+        if (share != null) {
+            fileOperationsHelper?.updateShareInformation(
+                share, permission, binding
+                    .shareProcessHideDownloadCheckbox.isChecked,
+                binding.shareProcessEnterPassword.text.toString().trim(),
+                chosenExpDateInMills, binding.shareProcessChangeNameEt.text.toString().trim()
+            )
+            //copy the share link if available
+            if (!TextUtils.isEmpty(share?.shareLink)) {
+                ClipboardUtil.copyToClipboard(activity, share?.shareLink)
+            }
+            removeCurrentFragment()
+        } else {
+            //else show step 2 (note screen)
+            showShareProcessSecond()
+        }
+    }
+
+    /**
+     * method to validate step 2 (note screen) information
+     */
+    private fun validateShareProcessSecond() {
+        if (TextUtils.isEmpty(binding.noteText.text.toString().trim())) {
+            DisplayUtils.showSnackMessage(binding.root, R.string.share_link_empty_note_message)
+            return
+        }
+        //if modifying existing share then directly update the note and send email
+        if (share != null) {
+            fileOperationsHelper?.updateNoteToShare(share, binding.noteText.text.toString().trim())
+        } else {
+            //else create new share
+            fileOperationsHelper?.shareFileWithSharee(
+                file,
+                shareeName,
+                shareType,
+                permission,
+                binding
+                    .shareProcessHideDownloadCheckbox.isChecked,
+                binding.shareProcessEnterPassword.text.toString().trim(),
+                chosenExpDateInMills,
+                binding.noteText.text.toString().trim(), binding.shareProcessChangeNameEt.text.toString().trim()
+            )
+        }
+        removeCurrentFragment()
+    }
+
+    /**
+     * method will be called from DrawerActivity on back press to handle screen backstack
+     */
+    fun onBackPressed() {
+        onCancelClick()
+    }
+
+    override fun onDateSet(year: Int, monthOfYear: Int, dayOfMonth: Int, chosenDateInMillis: Long) {
+        binding.shareProcessSelectExpDate.text = (resources.getString(
+            R.string.share_expiration_date_format,
+            SimpleDateFormat.getDateInstance().format(Date(chosenDateInMillis))
+        ))
+        this.chosenExpDateInMills = chosenDateInMillis
+    }
+
+    override fun onDateUnSet() {
+        binding.shareProcessSetExpDateSwitch.isChecked = false
+    }
+}

+ 164 - 0
src/main/java/com/owncloud/android/ui/fragment/QuickSharingPermissionsBottomSheetDialog.java

@@ -0,0 +1,164 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author TSI-mc
+ * Copyright (C) 2021 TSI-mc
+ * Copyright (C) 2021 Nextcloud GmbH
+ *
+ * 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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 <https://www.gnu.org/licenses/>.
+ */
+
+package com.owncloud.android.ui.fragment;
+
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.google.android.material.bottomsheet.BottomSheetBehavior;
+import com.google.android.material.bottomsheet.BottomSheetDialog;
+import com.owncloud.android.R;
+import com.owncloud.android.databinding.QuickSharingPermissionsBottomSheetFragmentBinding;
+import com.owncloud.android.datamodel.QuickPermissionModel;
+import com.owncloud.android.lib.resources.shares.OCShare;
+import com.owncloud.android.ui.activity.FileActivity;
+import com.owncloud.android.ui.adapter.QuickSharingPermissionsAdapter;
+import com.owncloud.android.ui.fragment.util.SharingMenuHelper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.recyclerview.widget.LinearLayoutManager;
+
+import static com.owncloud.android.lib.resources.shares.OCShare.CREATE_PERMISSION_FLAG;
+import static com.owncloud.android.lib.resources.shares.OCShare.MAXIMUM_PERMISSIONS_FOR_FILE;
+import static com.owncloud.android.lib.resources.shares.OCShare.MAXIMUM_PERMISSIONS_FOR_FOLDER;
+import static com.owncloud.android.lib.resources.shares.OCShare.READ_PERMISSION_FLAG;
+
+/**
+ * File Details Quick Sharing permissions options {@link android.app.Dialog} styled as a bottom sheet for main actions.
+ */
+public class QuickSharingPermissionsBottomSheetDialog extends BottomSheetDialog {
+    private QuickSharingPermissionsBottomSheetFragmentBinding binding;
+    private final QuickPermissionSharingBottomSheetActions actions;
+    private final FileActivity fileActivity;
+    private final OCShare ocShare;
+
+    public QuickSharingPermissionsBottomSheetDialog(FileActivity fileActivity,
+                                                    QuickPermissionSharingBottomSheetActions actions,
+                                                    OCShare ocShare) {
+        super(fileActivity);
+        this.actions = actions;
+        this.ocShare = ocShare;
+        this.fileActivity = fileActivity;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        binding = QuickSharingPermissionsBottomSheetFragmentBinding.inflate(getLayoutInflater());
+        setContentView(binding.getRoot());
+
+        if (getWindow() != null) {
+            getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+        }
+
+        setUpRecyclerView();
+        setOnShowListener(d ->
+                              BottomSheetBehavior.from((View) binding.getRoot().getParent())
+                                  .setPeekHeight(binding.getRoot().getMeasuredHeight())
+                         );
+    }
+
+    private void setUpRecyclerView() {
+        List<QuickPermissionModel> quickPermissionModelList = getQuickPermissionList();
+        QuickSharingPermissionsAdapter adapter = new QuickSharingPermissionsAdapter(quickPermissionModelList, new QuickSharingPermissionsAdapter.QuickSharingPermissionViewHolder.OnPermissionChangeListener() {
+            @Override
+            public void onPermissionChanged(int position) {
+                handlePermissionChanged(quickPermissionModelList, position);
+            }
+
+            @Override
+            public void onDismissSheet() {
+                dismiss();
+            }
+        });
+        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(fileActivity);
+        binding.rvQuickSharePermissions.setLayoutManager(linearLayoutManager);
+        binding.rvQuickSharePermissions.setAdapter(adapter);
+    }
+
+    /**
+     * handle permission changed on click of selected permission
+     * @param quickPermissionModelList
+     * @param position
+     */
+    private void handlePermissionChanged(List<QuickPermissionModel> quickPermissionModelList, int position) {
+        if (quickPermissionModelList.get(position).getPermissionName().equalsIgnoreCase(fileActivity.getResources().getString(R.string.link_share_allow_upload_and_editing))
+            || quickPermissionModelList.get(position).getPermissionName().equalsIgnoreCase(fileActivity.getResources().getString(R.string.link_share_editing))) {
+            if (ocShare.isFolder()) {
+                actions.onQuickPermissionChanged(ocShare,
+                                                 MAXIMUM_PERMISSIONS_FOR_FOLDER);
+            } else {
+                actions.onQuickPermissionChanged(ocShare,
+                                                 MAXIMUM_PERMISSIONS_FOR_FILE);
+            }
+        } else if (quickPermissionModelList.get(position).getPermissionName().equalsIgnoreCase(fileActivity.getResources().getString(R.string
+                                                                                                                                         .link_share_read_only))) {
+            actions.onQuickPermissionChanged(ocShare,
+                                             READ_PERMISSION_FLAG);
+
+        } else if (quickPermissionModelList.get(position).getPermissionName().equalsIgnoreCase(fileActivity.getResources().getString(R.string
+                                                                                                                                         .link_share_file_drop))) {
+            actions.onQuickPermissionChanged(ocShare,
+                                             CREATE_PERMISSION_FLAG);
+        }
+        dismiss();
+    }
+
+    /**
+     * prepare the list of permissions needs to be displayed on recyclerview
+     * @return
+     */
+    private List<QuickPermissionModel> getQuickPermissionList() {
+        List<QuickPermissionModel> quickPermissionModelList = new ArrayList<>();
+
+        String[] permissionArray;
+        if (ocShare.isFolder()) {
+            permissionArray =
+                fileActivity.getResources().getStringArray(R.array.folder_share_permission_dialog_values);
+        } else {
+            permissionArray =
+                fileActivity.getResources().getStringArray(R.array.file_share_permission_dialog_values);
+        }
+        //get the checked item position
+        int checkedItem = SharingMenuHelper.getPermissionCheckedItem(fileActivity, ocShare, permissionArray);
+
+        for (int i = 0; i < permissionArray.length; i++) {
+            QuickPermissionModel quickPermissionModel = new QuickPermissionModel(permissionArray[i], checkedItem == i);
+            quickPermissionModelList.add(quickPermissionModel);
+        }
+        return quickPermissionModelList;
+    }
+
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        binding = null;
+    }
+
+    public interface QuickPermissionSharingBottomSheetActions {
+        void onQuickPermissionChanged(OCShare share, int permission);
+    }
+}

+ 91 - 3
src/main/java/com/owncloud/android/ui/fragment/util/SharingMenuHelper.java

@@ -2,7 +2,9 @@
  * Nextcloud Android client application
  *
  * @author Andy Scherzinger
+ * @author TSI-mc
  * Copyright (C) 2018 Andy Scherzinger
+ * Copyright (C) 2021 TSI-mc
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
@@ -20,6 +22,7 @@
 
 package com.owncloud.android.ui.fragment.util;
 
+import android.content.Context;
 import android.content.res.Resources;
 import android.view.MenuItem;
 
@@ -29,6 +32,15 @@ import com.owncloud.android.lib.resources.shares.OCShare;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 
+import androidx.annotation.VisibleForTesting;
+
+import static com.owncloud.android.lib.resources.shares.OCShare.CREATE_PERMISSION_FLAG;
+import static com.owncloud.android.lib.resources.shares.OCShare.MAXIMUM_PERMISSIONS_FOR_FILE;
+import static com.owncloud.android.lib.resources.shares.OCShare.MAXIMUM_PERMISSIONS_FOR_FOLDER;
+import static com.owncloud.android.lib.resources.shares.OCShare.NO_PERMISSION;
+import static com.owncloud.android.lib.resources.shares.OCShare.READ_PERMISSION_FLAG;
+import static com.owncloud.android.lib.resources.shares.OCShare.SHARE_PERMISSION_FLAG;
+
 /**
  * Helper calls for visibility logic of the sharing menu.
  */
@@ -102,11 +114,87 @@ public final class SharingMenuHelper {
     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))
-            ));
+                R.string.share_expiration_date_label,
+                SimpleDateFormat.getDateInstance().format(new Date(expirationDateValue))
+                                                 ));
         } else {
             expirationDate.setTitle(R.string.share_no_expiration_date_label);
         }
     }
+
+    @VisibleForTesting
+    public static boolean isUploadAndEditingAllowed(OCShare share) {
+        if (share.getPermissions() == NO_PERMISSION) {
+            return false;
+        }
+
+        return (share.getPermissions() & (share.isFolder() ? MAXIMUM_PERMISSIONS_FOR_FOLDER :
+            MAXIMUM_PERMISSIONS_FOR_FILE)) == (share.isFolder() ? MAXIMUM_PERMISSIONS_FOR_FOLDER :
+            MAXIMUM_PERMISSIONS_FOR_FILE);
+    }
+
+    @VisibleForTesting
+    public static boolean isReadOnly(OCShare share) {
+        if (share.getPermissions() == NO_PERMISSION) {
+            return false;
+        }
+
+        return (share.getPermissions() & ~SHARE_PERMISSION_FLAG) == READ_PERMISSION_FLAG;
+    }
+
+    @VisibleForTesting
+    public static boolean isFileDrop(OCShare share) {
+        if (share.getPermissions() == NO_PERMISSION) {
+            return false;
+        }
+
+        return (share.getPermissions() & ~SHARE_PERMISSION_FLAG) == CREATE_PERMISSION_FLAG;
+    }
+
+    public static String getPermissionName(Context context, OCShare share) {
+        if (SharingMenuHelper.isUploadAndEditingAllowed(share)) {
+            return context.getResources().getString(R.string.share_permission_can_edit);
+        } else if (SharingMenuHelper.isReadOnly(share)) {
+            return context.getResources().getString(R.string.share_permission_view_only);
+        } else if (SharingMenuHelper.isFileDrop(share)) {
+            return context.getResources().getString(R.string.share_permission_file_drop);
+        }
+        return null;
+    }
+
+    /**
+     * method to get the current checked index from the list of permissions
+     *
+     * @param context
+     * @param share
+     * @param permissionArray
+     * @return
+     */
+    public static int getPermissionCheckedItem(Context context, OCShare share, String[] permissionArray) {
+        if (SharingMenuHelper.isUploadAndEditingAllowed(share)) {
+            if (share.isFolder()) {
+                return getPermissionIndexFromArray(context, permissionArray, R.string.link_share_allow_upload_and_editing);
+            } else {
+                return getPermissionIndexFromArray(context, permissionArray, R.string.link_share_editing);
+            }
+        } else if (SharingMenuHelper.isReadOnly(share)) {
+            return getPermissionIndexFromArray(context, permissionArray, R.string.link_share_read_only);
+        } else if (SharingMenuHelper.isFileDrop(share)) {
+            return getPermissionIndexFromArray(context, permissionArray, R.string.link_share_file_drop);
+        }
+        return 0;//default first item selected
+    }
+
+    private static int getPermissionIndexFromArray(Context context, String[] permissionArray, int permissionName) {
+        for (int i = 0; i < permissionArray.length; i++) {
+            if (permissionArray[i].equalsIgnoreCase(context.getResources().getString(permissionName))) {
+                return i;
+            }
+        }
+        return 0;
+    }
+
+    public static boolean canReshare(OCShare share) {
+        return (share.getPermissions() & SHARE_PERMISSION_FLAG) > 0;
+    }
 }

+ 75 - 1
src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java

@@ -6,10 +6,12 @@
  * @author Juan Carlos González Cabrero
  * @author Andy Scherzinger
  * @author Chris Narkiewicz
+ * @author TSI-mc
  *
  * Copyright (C) 2015 ownCloud Inc.
  * Copyright (C) 2018 Andy Scherzinger
  * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
+ * Copyright (C) 2021 TSI-mc
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2,
@@ -244,7 +246,7 @@ public class FileOperationsHelper {
 
     private void syncFile(OCFile file, User user, FileDataStorageManager storageManager) {
         fileActivity.runOnUiThread(() -> fileActivity.showLoadingDialog(fileActivity.getResources()
-                                                                            .getString(R.string.sync_in_progress)));
+                .getString(R.string.sync_in_progress)));
 
         SynchronizeFileOperation sfo = new SynchronizeFileOperation(file,
                                                                     null,
@@ -530,6 +532,50 @@ public class FileOperationsHelper {
         }
     }
 
+
+    /**
+     * Helper method to share a file with a known sharee. Starts a request to do it in {@link OperationsService}
+     *
+     * @param file                   The file to share.
+     * @param shareeName             Name (user name or group name) of the target sharee.
+     * @param shareType              The share type determines the sharee type.
+     * @param permissions            Permissions to grant to sharee on the shared file.
+     * @param hideFileDownload       true/false to hide file download
+     * @param password               Password to set for the public link; null or empty string to clear the current
+     *                               password
+     * @param expirationTimeInMillis Expiration date to set. A negative value clears the current expiration date,
+     *                               leaving the link unrestricted. Zero makes no change.
+     * @param note                   note message for the receiver. Null or empty for no message
+     * @param label                  new label
+     */
+    public void shareFileWithSharee(OCFile file, String shareeName, ShareType shareType, int permissions,
+                                    boolean hideFileDownload, String password, long expirationTimeInMillis,
+                                    String note, String label) {
+        if (file != null) {
+            // TODO check capability?
+            fileActivity.showLoadingDialog(fileActivity.getApplicationContext().
+                getString(R.string.wait_a_moment));
+
+            Intent service = new Intent(fileActivity, OperationsService.class);
+            service.setAction(OperationsService.ACTION_CREATE_SHARE_WITH_SHAREE);
+            service.putExtra(OperationsService.EXTRA_ACCOUNT, fileActivity.getAccount());
+            service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
+            service.putExtra(OperationsService.EXTRA_SHARE_WITH, shareeName);
+            service.putExtra(OperationsService.EXTRA_SHARE_TYPE, shareType);
+            service.putExtra(OperationsService.EXTRA_SHARE_PERMISSIONS, permissions);
+            service.putExtra(OperationsService.EXTRA_SHARE_HIDE_FILE_DOWNLOAD, hideFileDownload);
+            service.putExtra(OperationsService.EXTRA_SHARE_PASSWORD, (password == null) ? "" : password);
+            service.putExtra(OperationsService.EXTRA_SHARE_EXPIRATION_DATE_IN_MILLIS, expirationTimeInMillis);
+            service.putExtra(OperationsService.EXTRA_SHARE_NOTE, note);
+            service.putExtra(OperationsService.EXTRA_SHARE_PUBLIC_LABEL, (label == null) ? "" : label);
+
+            mWaitingForOpId = fileActivity.getOperationsServiceBinder().queueNewOperation(service);
+
+        } else {
+            Log_OC.e(TAG, "Trying to share a NULL OCFile");
+        }
+    }
+
     /**
      * Helper method to revert to a file version. Starts a request to do it in {@link OperationsService}
      *
@@ -699,6 +745,34 @@ public class FileOperationsHelper {
         queueShareIntent(updateShareIntent);
     }
 
+    /**
+     * Helper method to update the share information
+     *
+     * @param share                  {@link OCShare} instance which information will be updated.
+     * @param permissions            Permissions to grant to sharee on the shared file.
+     * @param hideFileDownload       true/false to hide file download
+     * @param password               Password to set for the public link; null or empty string to clear the current
+     *                               password
+     * @param expirationTimeInMillis Expiration date to set. A negative value clears the current expiration date,
+     *                               leaving the link unrestricted. Zero makes no change.
+     * @param label                  new label
+     */
+    public void updateShareInformation(OCShare share, int permissions,
+                                       boolean hideFileDownload, String password, long expirationTimeInMillis,
+                                       String label) {
+        Intent updateShareIntent = new Intent(fileActivity, OperationsService.class);
+        updateShareIntent.setAction(OperationsService.ACTION_UPDATE_SHARE_INFO);
+        updateShareIntent.putExtra(OperationsService.EXTRA_ACCOUNT, fileActivity.getAccount());
+        updateShareIntent.putExtra(OperationsService.EXTRA_SHARE_ID, share.getId());
+        updateShareIntent.putExtra(OperationsService.EXTRA_SHARE_PERMISSIONS, permissions);
+        updateShareIntent.putExtra(OperationsService.EXTRA_SHARE_HIDE_FILE_DOWNLOAD, hideFileDownload);
+        updateShareIntent.putExtra(OperationsService.EXTRA_SHARE_PASSWORD, (password == null) ? "" : password);
+        updateShareIntent.putExtra(OperationsService.EXTRA_SHARE_EXPIRATION_DATE_IN_MILLIS, expirationTimeInMillis);
+        updateShareIntent.putExtra(OperationsService.EXTRA_SHARE_PUBLIC_LABEL, (label == null) ? "" : label);
+        queueShareIntent(updateShareIntent);
+    }
+
+
     public void sendShareFile(OCFile file, boolean hideNcSharingOptions) {
         // Show dialog
         FragmentManager fm = fileActivity.getSupportFragmentManager();

+ 30 - 0
src/main/res/drawable/ic_baseline_arrow_drop_down_24.xml

@@ -0,0 +1,30 @@
+<!--
+ Nextcloud Android client application
+
+ @author TSI-mc
+ Copyright (C) 2021 TSI-mc
+ Copyright (C) 2021 Nextcloud GmbH
+
+ 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
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU 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 <https://www.gnu.org/licenses/>.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M7,10l5,5 5,-5z"/>
+</vector>

+ 30 - 0
src/main/res/drawable/ic_baseline_check_24.xml

@@ -0,0 +1,30 @@
+<!--
+ Nextcloud Android client application
+
+ @author TSI-mc
+ Copyright (C) 2021 TSI-mc
+ Copyright (C) 2021 Nextcloud GmbH
+
+ 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
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU 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 <https://www.gnu.org/licenses/>.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
+</vector>

+ 9 - 0
src/main/res/layout/file_details_fragment.xml

@@ -1,9 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?><!--
   ownCloud Android client application
 
+  @author TSI-mc
+
   Copyright (C) 2012 Bartek Przybylski
   Copyright (C) 2017 ownCloud GmbH.
   Copyright (C) 2018 Andy Scherzinger
+  Copyright (C) 2021 TSI-mc
 
   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License version 2,
@@ -191,6 +194,12 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
 
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:visibility="gone"
+        android:id="@+id/sharing_frame_container"
+        android:layout_height="match_parent"/>
+
     <include
         android:id="@+id/empty_list"
         layout="@layout/empty_list" />

+ 36 - 12
src/main/res/layout/file_details_share_link_share_item.xml

@@ -1,12 +1,14 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
   Nextcloud Android client application
 
   @author Tobias Kaminsky
   @author Andy Scherzinger
+  @author TSI-mc
+
   Copyright (C) 2020 Andy Scherzinger
   Copyright (C) 2020 Tobias Kaminsky
   Copyright (C) 2020 Nextcloud GmbH
+  Copyright (C) 2021 TSI-mc
 
   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
@@ -23,6 +25,8 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/share_by_link_container"
     android:layout_width="match_parent"
     android:layout_height="@dimen/sharee_list_item_size"
@@ -40,18 +44,38 @@
         android:padding="@dimen/standard_half_padding"
         android:src="@drawable/shared_via_link" />
 
-    <TextView
-        android:id="@+id/name"
+    <LinearLayout
+        android:id="@+id/share_name_layout"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:layout_gravity="center_vertical"
-        android:textColor="@color/text_color"
-        android:ellipsize="end"
-        android:singleLine="true"
         android:layout_weight="1"
-        android:gravity="center_vertical"
-        android:textSize="@dimen/two_line_primary_text_size"
-        android:text="@string/share_via_link_section_title" />
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/name"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:ellipsize="end"
+            android:gravity="center_vertical"
+            android:singleLine="true"
+            android:text="@string/share_via_link_section_title"
+            android:textColor="@color/text_color"
+            android:textSize="@dimen/two_line_primary_text_size" />
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/permission_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:singleLine="true"
+            tools:text="View only"
+            app:drawableEndCompat="@drawable/ic_baseline_arrow_drop_down_24"
+            app:drawableTint="@color/primary"
+            app:drawableRightCompat="@drawable/ic_baseline_arrow_drop_down_24"
+            android:textColor="@color/primary"
+            android:textSize="@dimen/two_line_secondary_text_size" />
+    </LinearLayout>
 
     <ImageView
         android:id="@+id/copy_link"
@@ -59,11 +83,11 @@
         android:layout_height="wrap_content"
         android:layout_gravity="center_vertical"
         android:contentDescription="@string/copy_link"
+        android:paddingStart="@dimen/standard_padding"
         android:paddingTop="@dimen/standard_quarter_margin"
+        android:paddingEnd="@dimen/standard_padding"
         android:paddingBottom="@dimen/standard_quarter_margin"
         android:scaleType="fitCenter"
-        android:paddingStart="@dimen/standard_padding"
-        android:paddingEnd="@dimen/standard_padding"
         android:src="@drawable/ic_content_copy" />
 
     <ImageView

+ 48 - 25
src/main/res/layout/file_details_share_share_item.xml

@@ -1,9 +1,11 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
   ownCloud Android client application
 
+  @author TSI-mc
+
   Copyright (C) 2015 ownCloud Inc.
   Copyright (C) 2018 Andy Scherzinger
+  Copyright (C) 2021 TSI-mc
 
   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License version 2,
@@ -18,39 +20,60 @@
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 -->
 <LinearLayout 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"
     android:layout_width="match_parent"
-    android:orientation="horizontal"
     android:layout_height="@dimen/sharee_list_item_size"
+    android:orientation="horizontal"
     android:weightSum="1"
     tools:ignore="UseCompoundDrawables">
 
-        <ImageView
-            android:id="@+id/icon"
-            android:layout_width="@dimen/user_icon_size"
-            android:layout_height="@dimen/user_icon_size"
-            android:layout_gravity="center_vertical"
-            android:layout_marginBottom="@dimen/standard_half_margin"
-            android:layout_marginEnd="@dimen/standard_margin"
-            android:layout_marginLeft="@dimen/standard_margin"
-            android:layout_marginRight="@dimen/standard_margin"
-            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:id="@+id/name"
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="@dimen/user_icon_size"
+        android:layout_height="@dimen/user_icon_size"
+        android:layout_gravity="center_vertical"
+        android:layout_marginStart="@dimen/standard_margin"
+        android:layout_marginLeft="@dimen/standard_margin"
+        android:layout_marginTop="@dimen/standard_half_margin"
+        android:layout_marginEnd="@dimen/standard_margin"
+        android:layout_marginRight="@dimen/standard_margin"
+        android:layout_marginBottom="@dimen/standard_half_margin"
+        android:contentDescription="@string/user_icon"
+        android:src="@drawable/ic_user" />
+
+    <LinearLayout
+        android:id="@+id/share_name_layout"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:layout_gravity="center_vertical"
-        android:textColor="@color/text_color"
-        android:ellipsize="end"
-        android:singleLine="true"
         android:layout_weight="1"
-        android:gravity="center_vertical"
-        android:textSize="@dimen/two_line_primary_text_size"
-        android:text="@string/username" />
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/name"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:ellipsize="end"
+            android:gravity="center_vertical"
+            android:singleLine="true"
+            android:text="@string/username"
+            android:textColor="@color/text_color"
+            android:textSize="@dimen/two_line_primary_text_size" />
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/permission_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:singleLine="true"
+            android:textColor="@color/primary"
+            android:textSize="@dimen/two_line_secondary_text_size"
+            app:drawableEndCompat="@drawable/ic_baseline_arrow_drop_down_24"
+            app:drawableRightCompat="@drawable/ic_baseline_arrow_drop_down_24"
+            app:drawableTint="@color/primary"
+            tools:text="View only" />
+    </LinearLayout>
 
     <ImageView
         android:id="@+id/overflow_menu"

+ 222 - 0
src/main/res/layout/file_details_sharing_menu_bottom_sheet_fragment.xml

@@ -0,0 +1,222 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Nextcloud Android client application
+
+ @author TSI-mc
+ Copyright (C) 2021 TSI-mc
+ Copyright (C) 2021 Nextcloud GmbH
+
+ 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
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU 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 <https://www.gnu.org/licenses/>.
+-->
+<LinearLayout 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"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@color/bg_default"
+    android:orientation="vertical"
+    android:paddingTop="@dimen/standard_padding">
+
+    <LinearLayout
+        android:id="@+id/menu_share_open_in"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="?android:attr/selectableItemBackground"
+        android:orientation="horizontal"
+        android:paddingLeft="@dimen/standard_padding"
+        android:paddingTop="@dimen/standard_half_padding"
+        android:paddingRight="@dimen/standard_padding"
+        android:paddingBottom="@dimen/standard_half_padding"
+        tools:ignore="UseCompoundDrawables">
+
+        <ImageView
+            android:id="@+id/menu_icon_open_in"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:contentDescription="@null"
+            android:src="@drawable/ic_content_copy"
+            app:tint="@color/primary" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:layout_marginStart="@dimen/standard_margin"
+            android:text="@string/share_open_in"
+            android:textColor="@color/text_color"
+            android:textSize="@dimen/bottom_sheet_text_size" />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/menu_share_advanced_permissions"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="?android:attr/selectableItemBackground"
+        android:orientation="horizontal"
+        android:paddingLeft="@dimen/standard_padding"
+        android:paddingTop="@dimen/standard_half_padding"
+        android:paddingRight="@dimen/standard_padding"
+        android:paddingBottom="@dimen/standard_half_padding"
+        tools:ignore="UseCompoundDrawables">
+
+        <ImageView
+            android:id="@+id/menu_icon_advanced_permissions"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:contentDescription="@null"
+            android:src="@drawable/ic_edit"
+            app:tint="@color/primary" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:layout_marginStart="@dimen/standard_margin"
+            android:text="@string/share_settings"
+            android:textColor="@color/text_color"
+            android:textSize="@dimen/bottom_sheet_text_size" />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/menu_share_send_new_email"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="?android:attr/selectableItemBackground"
+        android:orientation="horizontal"
+        android:paddingLeft="@dimen/standard_padding"
+        android:paddingTop="@dimen/standard_half_padding"
+        android:paddingRight="@dimen/standard_padding"
+        android:paddingBottom="@dimen/standard_half_padding"
+        tools:ignore="UseCompoundDrawables">
+
+        <ImageView
+            android:id="@+id/menu_icon_send_new_email"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:contentDescription="@null"
+            android:src="@drawable/ic_email"
+            app:tint="@color/primary" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:layout_marginStart="@dimen/standard_margin"
+            android:text="@string/share_send_new_email"
+            android:textColor="@color/text_color"
+            android:textSize="@dimen/bottom_sheet_text_size" />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/menu_share_send_link"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="?android:attr/selectableItemBackground"
+        android:orientation="horizontal"
+        android:paddingLeft="@dimen/standard_padding"
+        android:paddingTop="@dimen/standard_half_padding"
+        android:paddingRight="@dimen/standard_padding"
+        android:paddingBottom="@dimen/standard_half_padding"
+        android:visibility="gone"
+        tools:ignore="UseCompoundDrawables"
+        tools:visibility="visible">
+
+        <ImageView
+            android:id="@+id/menu_icon_send_link"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:contentDescription="@null"
+            android:src="@drawable/ic_link"
+            app:tint="@color/primary" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:layout_marginStart="@dimen/standard_margin"
+            android:text="@string/share_via_link_send_link_label"
+            android:textColor="@color/text_color"
+            android:textSize="@dimen/bottom_sheet_text_size" />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/menu_share_unshare"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="?android:attr/selectableItemBackground"
+        android:orientation="horizontal"
+        android:paddingLeft="@dimen/standard_padding"
+        android:paddingTop="@dimen/standard_half_padding"
+        android:paddingRight="@dimen/standard_padding"
+        android:paddingBottom="@dimen/standard_half_padding"
+        tools:ignore="UseCompoundDrawables">
+
+        <ImageView
+            android:id="@+id/menu_icon_unshare"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:contentDescription="@null"
+            android:src="@drawable/ic_delete"
+            app:tint="@color/primary" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:layout_marginStart="@dimen/standard_margin"
+            android:text="@string/delete_link"
+            android:textColor="@color/text_color"
+            android:textSize="@dimen/bottom_sheet_text_size" />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/menu_share_add_another_link"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="?android:attr/selectableItemBackground"
+        android:orientation="horizontal"
+        android:paddingLeft="@dimen/standard_padding"
+        android:paddingTop="@dimen/standard_half_padding"
+        android:paddingRight="@dimen/standard_padding"
+        android:paddingBottom="@dimen/standard_half_padding"
+        android:visibility="gone"
+        tools:ignore="UseCompoundDrawables"
+        tools:visibility="visible">
+
+        <ImageView
+            android:id="@+id/menu_icon_add_another_link"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:contentDescription="@null"
+            android:src="@drawable/ic_plus"
+            app:tint="@color/primary" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:layout_marginStart="@dimen/standard_margin"
+            android:text="@string/add_another_public_share_link"
+            android:textColor="@color/text_color"
+            android:textSize="@dimen/bottom_sheet_text_size" />
+
+    </LinearLayout>
+
+
+</LinearLayout>

+ 279 - 0
src/main/res/layout/file_details_sharing_process_fragment.xml

@@ -0,0 +1,279 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Nextcloud Android client application
+
+ @author TSI-mc
+ Copyright (C) 2021 TSI-mc
+ Copyright (C) 2021 Nextcloud GmbH
+
+ 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
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU 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 <https://www.gnu.org/licenses/>.
+-->
+<androidx.constraintlayout.widget.ConstraintLayout 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"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/bg_default"
+    android:focusable="true"
+    android:focusableInTouchMode="true">
+
+    <androidx.core.widget.NestedScrollView
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:padding="@dimen/standard_padding"
+        app:layout_constraintBottom_toTopOf="@+id/share_process_btn_cancel"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:id="@+id/share_process_edit_share_link"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/standard_margin"
+                android:text="@string/share_permissions"
+                android:textColor="@color/secondary_text_color"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <RadioGroup
+                android:id="@+id/share_process_permission_radio_group"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/share_process_edit_share_link">
+
+                <androidx.appcompat.widget.AppCompatRadioButton
+                    android:id="@+id/share_process_permission_read_only"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/link_share_read_only" />
+
+                <androidx.appcompat.widget.AppCompatRadioButton
+                    android:id="@+id/share_process_permission_upload_editing"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/link_share_allow_upload_and_editing" />
+
+                <androidx.appcompat.widget.AppCompatRadioButton
+                    android:id="@+id/share_process_permission_file_drop"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/link_share_file_drop" />
+
+            </RadioGroup>
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:id="@+id/share_process_advance_permission_title"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/standard_margin"
+                android:text="@string/advanced_settings"
+                android:textColor="@color/secondary_text_color"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/share_process_permission_radio_group" />
+
+            <androidx.appcompat.widget.AppCompatCheckBox
+                android:id="@+id/share_process_allow_resharing_checkbox"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:text="@string/allow_resharing"
+                android:visibility="gone"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/share_process_advance_permission_title"
+                tools:visibility="visible" />
+
+            <androidx.appcompat.widget.SwitchCompat
+                android:id="@+id/share_process_set_password_switch"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/share_no_password_title"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/share_process_allow_resharing_checkbox" />
+
+            <androidx.appcompat.widget.AppCompatEditText
+                android:id="@+id/share_process_enter_password"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:autofillHints="password"
+                android:hint="@string/hint_password"
+                android:inputType="textPassword"
+                android:visibility="gone"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/share_process_set_password_switch"
+                tools:visibility="visible" />
+
+            <androidx.appcompat.widget.SwitchCompat
+                android:id="@+id/share_process_set_exp_date_switch"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/standard_half_margin"
+                android:text="@string/share_no_expiration_date_label"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/share_process_enter_password" />
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:id="@+id/share_process_select_exp_date"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center_vertical"
+                android:padding="@dimen/standard_half_padding"
+                android:visibility="gone"
+                app:drawableEndCompat="@drawable/file_calendar"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/share_process_set_exp_date_switch"
+                tools:visibility="visible" />
+
+            <View
+                android:id="@+id/share_process_exp_date_divider"
+                android:layout_width="match_parent"
+                android:layout_height="0.5dp"
+                android:background="@color/black"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/share_process_select_exp_date" />
+
+            <androidx.appcompat.widget.SwitchCompat
+                android:id="@+id/share_process_hide_download_checkbox"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/standard_half_margin"
+                android:text="@string/share_via_link_hide_download"
+                android:visibility="gone"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/share_process_exp_date_divider"
+                tools:visibility="visible" />
+
+            <androidx.appcompat.widget.SwitchCompat
+                android:id="@+id/share_process_change_name_switch"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/standard_half_margin"
+                android:text="@string/link_name"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/share_process_hide_download_checkbox" />
+
+            <androidx.appcompat.widget.AppCompatEditText
+                android:id="@+id/share_process_change_name_et"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:hint="@string/hint_name"
+                android:importantForAutofill="no"
+                android:inputType="textNoSuggestions|textCapSentences"
+                android:visibility="gone"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/share_process_change_name_switch"
+                tools:visibility="visible" />
+
+            <androidx.constraintlayout.widget.Group
+                android:id="@+id/share_process_group_one"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:visibility="visible"
+                app:constraint_referenced_ids="share_process_exp_date_divider,
+                share_process_permission_radio_group,
+                share_process_advance_permission_title, share_process_hide_download_checkbox,
+                share_process_allow_resharing_checkbox, share_process_set_password_switch,
+                share_process_set_exp_date_switch, share_process_enter_password,
+                share_process_select_exp_date, share_process_change_name_switch,
+                share_process_change_name_et" />
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:id="@+id/share_process_message_title"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/standard_margin"
+                android:text="@string/share_send_note"
+                android:textColor="@color/secondary_text_color"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/share_process_edit_share_link"/>
+
+            <com.google.android.material.textfield.TextInputLayout
+                android:id="@+id/note_container"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/standard_half_margin"
+                android:hint="@string/hint_note"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/share_process_message_title">
+
+                <com.google.android.material.textfield.TextInputEditText
+                    android:id="@+id/note_text"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:gravity="top"
+                    android:importantForAutofill="no"
+                    android:inputType="textCapSentences|textMultiLine|textNoSuggestions"
+                    android:minLines="10"
+                    android:scrollbars="vertical" />
+            </com.google.android.material.textfield.TextInputLayout>
+
+            <androidx.constraintlayout.widget.Group
+                android:id="@+id/share_process_group_two"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:visibility="gone"
+                app:constraint_referenced_ids="share_process_message_title, note_container" />
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+    </androidx.core.widget.NestedScrollView>
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/share_process_btn_cancel"
+        style="@style/OutlinedButton"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/standard_margin"
+        android:layout_marginEnd="@dimen/standard_half_margin"
+        android:layout_marginBottom="@dimen/standard_margin"
+        android:text="@string/common_cancel"
+        app:cornerRadius="@dimen/button_corner_radius"
+        app:layout_constraintTop_toTopOf="@+id/share_process_btn_next"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@+id/share_process_btn_next"
+        app:layout_constraintStart_toStartOf="parent" />
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/share_process_btn_next"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/standard_half_margin"
+        android:layout_marginEnd="@dimen/standard_margin"
+        android:layout_marginBottom="@dimen/standard_margin"
+        android:text="@string/common_next"
+        android:theme="@style/Button.Primary"
+        app:cornerRadius="@dimen/button_corner_radius"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/share_process_btn_cancel" />
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 52 - 0
src/main/res/layout/item_quick_share_permissions.xml

@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Nextcloud Android client application
+
+ @author TSI-mc
+ Copyright (C) 2021 TSI-mc
+ Copyright (C) 2021 Nextcloud GmbH
+
+ 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
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU 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 <https://www.gnu.org/licenses/>.
+-->
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:padding="@dimen/standard_padding">
+
+    <ImageView
+        android:id="@+id/tv_quick_share_check_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:contentDescription="@null"
+        android:src="@drawable/ic_baseline_check_24"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:tint="@color/primary" />
+
+    <TextView
+        android:id="@+id/tv_quick_share_name"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginStart="@dimen/standard_margin"
+        android:text="@string/link_share_read_only"
+        android:textColor="@color/text_color"
+        android:textSize="@dimen/bottom_sheet_text_size"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/tv_quick_share_check_icon"
+        app:layout_constraintTop_toTopOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 36 - 0
src/main/res/layout/quick_sharing_permissions_bottom_sheet_fragment.xml

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Nextcloud Android client application
+
+ @author TSI-mc
+ Copyright (C) 2021 TSI-mc
+ Copyright (C) 2021 Nextcloud GmbH
+
+ 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
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU 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 <https://www.gnu.org/licenses/>.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@color/bg_default"
+    android:paddingTop="@dimen/standard_padding"
+    android:paddingBottom="@dimen/standard_padding"
+    android:orientation="vertical">
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/rv_quick_share_permissions"
+        android:layout_width="match_parent"
+        tools:listitem="@layout/item_quick_share_permissions"
+        android:layout_height="wrap_content"/>
+
+</LinearLayout>

+ 3 - 0
src/main/res/menu/fragment_file_detail_sharing_email_link.xml

@@ -1,7 +1,9 @@
 <?xml version="1.0" encoding="utf-8"?><!--
   Nextcloud Android client application
+  @author TSI-mc
 
   Copyright (C) 2018 Andy Scherzinger
+  Copyright (C) 2021 TSI-mc
 
   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
@@ -38,6 +40,7 @@
         android:checkable="true"
         android:showAsAction="never"
         android:title="@string/allow_editing"
+        android:visible="false"
         app:showAsAction="never" />
     <item
         android:id="@+id/action_hide_file_download"

+ 4 - 0
src/main/res/menu/fragment_file_detail_sharing_public_link.xml

@@ -1,7 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?><!--
   Nextcloud Android client application
 
+  @author TSI-mc
+
   Copyright (C) 2018 Andy Scherzinger
+  Copyright (C) 2021 TSI-mc
 
   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
@@ -37,6 +40,7 @@
         android:id="@+id/allow_editing"
         android:checkable="true"
         android:showAsAction="never"
+        android:visible="false"
         android:title="@string/allow_editing"
         app:showAsAction="never" />
     <item

+ 21 - 0
src/main/res/menu/item_user_sharing_settings.xml

@@ -2,7 +2,10 @@
 <!--
   Nesxtcloud Android client application
 
+  @author TSI-mc
+
   Copyright (C) 2018 Andy Scherzinger
+  Copyright (C) 2021 TSI-mc
 
   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
@@ -21,10 +24,25 @@
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     tools:ignore="AppCompatResource">
+    <group
+        android:id="@+id/folder_permission"
+        android:checkableBehavior="single">
+        <item
+            android:id="@+id/link_share_read_only"
+            android:title="@string/link_share_read_only" />
+        <item
+            android:id="@+id/link_share_allow_upload_and_editing"
+            android:title="@string/link_share_allow_upload_and_editing" />
+        <item
+            android:id="@+id/link_share_file_drop"
+            android:title="@string/link_share_file_drop" />
+    </group>
+
     <item
         android:id="@+id/allow_editing"
         android:checkable="true"
         android:showAsAction="never"
+        android:visible="false"
         android:title="@string/allow_editing"
         app:showAsAction="never" />
 
@@ -32,14 +50,17 @@
         android:id="@+id/allow_creating"
         android:checkable="true"
         android:showAsAction="never"
+        android:visible="false"
         android:title="@string/allow_creating"
         app:showAsAction="never" />
     <item
         android:id="@+id/allow_deleting"
         android:checkable="true"
         android:showAsAction="never"
+        android:visible="false"
         android:title="@string/allow_deleting"
         app:showAsAction="never" />
+
     <item
         android:id="@+id/allow_resharing"
         android:checkable="true"

+ 20 - 0
src/main/res/values-b+en+001/strings.xml

@@ -383,6 +383,7 @@
     <string name="link_share_allow_upload_and_editing">Allow upload and editing</string>
     <string name="link_share_file_drop">File drop (upload only)</string>
     <string name="link_share_read_only">Read only</string>
+    <string name="link_share_editing">Editing</string>
     <string name="list_layout">Listed layout</string>
     <string name="local_file_list_empty">There are no files in this folder.</string>
     <string name="local_file_not_found_message">File not found in local file system</string>
@@ -831,6 +832,25 @@
     <string name="whats_your_status">What is your status?</string>
     <string name="wrong_storage_path">Data storage folder does not exist!</string>
     <string name="wrong_storage_path_desc">This might be due to a backup restore on another device. Falling back to default. Please check settings to adjust data storage folder.</string>
+    <string name="share_permission_view_only">View only</string>
+    <string name="share_permission_can_edit">Can edit</string>
+    <string name="share_permission_file_drop">File drop</string>
+    <string name="share_permissions">Share Permissions</string>
+    <string name="advanced_settings">Advanced Settings</string>
+    <string name="common_next">Next</string>
+    <string name="send_share">Send Share</string>
+    <string name="no_share_permission_selected">Please select at least one permission to share.</string>
+    <string name="share_link_empty_exp_date">You must select expiration date.</string>
+    <string name="share_link_empty_note_message">Please enter note.</string>
+    <string name="share_copy_link">Share &amp; Copy Link</string>
+    <string name="set_note">Set Note</string>
+    <string name="share_open_in">Open in...</string>
+    <string name="share_advanced_permissions">Advanced permissions</string>
+    <string name="share_send_new_email">Send new email</string>
+    <string name="link_name">Link Name</string>
+    <string name="delete_link">Delete Link</string>
+    <string name="share_settings">Settings</string>
+    <string name="common_confirm">Confirm</string>
     <plurals name="sync_fail_in_favourites_content">
         <item quantity="one">Could not sync %1$d file (conflicts: %2$d)</item>
         <item quantity="other">Could not sync %1$d files (conflicts: %2$d)</item>

+ 23 - 3
src/main/res/values-de/strings.xml

@@ -383,9 +383,10 @@
     <string name="invisible">Unsichtbar</string>
     <string name="label_empty">Bezeichnung darf nicht leer sein</string>
     <string name="link">Link</string>
-    <string name="link_share_allow_upload_and_editing">Hochladen und Bearbeiten erlauben</string>
-    <string name="link_share_file_drop">Dateien ablegen (nur Hochladen)</string>
-    <string name="link_share_read_only">Schreibgeschützt</string>
+    <string name="link_share_allow_upload_and_editing">Hochladen &amp; Bearbeiten</string>
+    <string name="link_share_file_drop">Sammelbox</string>
+    <string name="link_share_read_only">Nur Lesen</string>
+    <string name="link_share_editing">Bearbeiten</string>
     <string name="list_layout">Layout der Liste</string>
     <string name="local_file_list_empty">Es befinden sich keine Dateien in diesem Ordner.</string>
     <string name="local_file_not_found_message">Die Datei wurde im lokalen Dateisystem nicht gefunden</string>
@@ -837,6 +838,25 @@
     <string name="whats_your_status">Wie ist Ihr Status?</string>
     <string name="wrong_storage_path">Speicherordner existiert nicht!</string>
     <string name="wrong_storage_path_desc">Ursache könnte die Wiederherstellung einer Sicherungskopie auf einem anderen Gerät sein. Der Standard-Ordner wird jetzt wieder verwendet. Bitte überprüfen Sie die Einstellungen bezüglich des Speicherortes.</string>
+    <string name="share_permission_view_only">Nur anschauen</string>
+    <string name="share_permission_can_edit">Kann bearbeiten</string>
+    <string name="share_permission_file_drop">Dateiablegen</string>
+    <string name="share_permissions">Berechtigungen teilen</string>
+    <string name="advanced_settings">Erweiterte Einstellungen</string>
+    <string name="common_next">Nächster</string>
+    <string name="send_share">Senden Teilen</string>
+    <string name="no_share_permission_selected">Bitte wählen Sie mindestens eine Berechtigung zum Teilen aus.</string>
+    <string name="share_link_empty_exp_date">Sie müssen das Ablaufdatum auswählen.</string>
+    <string name="share_link_empty_note_message">Bitte Anmerkung eingeben.</string>
+    <string name="share_copy_link">Link teilen &amp; kopieren</string>
+    <string name="set_note">Notiz setzen</string>
+    <string name="share_open_in">Öffnen mit...</string>
+    <string name="share_advanced_permissions">Erweiterte Berechtigungen</string>
+    <string name="share_send_new_email">Neue Email versenden</string>
+    <string name="link_name">Linkname</string>
+    <string name="delete_link">Link löschen</string>
+    <string name="share_settings">Einstellungen</string>
+    <string name="common_confirm">Bestätigen</string>
     <plurals name="sync_fail_in_favourites_content">
         <item quantity="one">Inhalte von %1$d Datei konnten nicht synchronisiert werden (Konflikte: %2$d)</item>
         <item quantity="other">Inhalte von %1$d Dateien konnten nicht synchronisiert werden (Konflikte: %2$d)</item>

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

@@ -18,4 +18,15 @@
         <item>@string/pref_instant_name_collision_policy_entries_rename</item>
         <item>@string/pref_instant_name_collision_policy_entries_cancel</item>
     </string-array>
+
+    <string-array name="folder_share_permission_dialog_values" translatable="false">
+        <item>@string/link_share_read_only</item>
+        <item>@string/link_share_allow_upload_and_editing</item>
+        <item>@string/link_share_file_drop</item>
+    </string-array>
+
+    <string-array name="file_share_permission_dialog_values" translatable="false">
+        <item>@string/link_share_read_only</item>
+        <item>@string/link_share_editing</item>
+    </string-array>
 </resources>

+ 21 - 0
src/main/res/values/strings.xml

@@ -455,6 +455,7 @@
     <string name="share_file">Share %1$s</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_expiration_date_format">%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_send_link_label">Send link</string>
@@ -922,6 +923,7 @@
     <string name="allow_deleting">Allow deleting</string>
     <string name="allow_resharing">Allow resharing</string>
     <string name="link_share_read_only">Read only</string>
+    <string name="link_share_editing">Editing</string>
     <string name="link_share_allow_upload_and_editing">Allow upload and editing</string>
     <string name="link_share_file_drop">File drop (upload only)</string>
     <string name="could_not_retrieve_shares">Could not retrieve shares</string>
@@ -960,6 +962,25 @@
     <string name="create">Create</string>
     <string name="select_one_template">Please select one template</string>
     <string name="choose_template_helper_text">Please choose a template and enter a file name.</string>
+    <string name="share_permission_view_only">View only</string>
+    <string name="share_permission_can_edit">Can edit</string>
+    <string name="share_permission_file_drop">File drop</string>
+    <string name="share_permissions">Share Permissions</string>
+    <string name="advanced_settings">Advanced Settings</string>
+    <string name="common_next">Next</string>
+    <string name="send_share">Send Share</string>
+    <string name="no_share_permission_selected">Please select at least one permission to share.</string>
+    <string name="share_link_empty_exp_date">You must select expiration date.</string>
+    <string name="share_link_empty_note_message">Please enter note.</string>
+    <string name="share_copy_link">Share &amp; Copy Link</string>
+    <string name="set_note">Set Note</string>
+    <string name="share_open_in">Open in…</string>
+    <string name="share_advanced_permissions">Advanced permissions</string>
+    <string name="share_send_new_email">Send new email</string>
+    <string name="link_name">Link Name</string>
+    <string name="delete_link">Delete Link</string>
+    <string name="share_settings">Settings</string>
+    <string name="common_confirm">Confirm</string>
     <string name="strict_mode">Strict mode: no HTTP connection allowed!</string>
     <string name="fullscreen">Fullscreen</string>
     <string name="more">More</string>

+ 1 - 0
src/test/java/com/owncloud/android/ui/adapter/ShareeListAdapterTest.kt

@@ -42,6 +42,7 @@ class ShareeListAdapterTest {
     private val fileActivity: FileActivity? = null
 
     @Test
+    @Suppress("LongMethod")
     fun testSorting() {
         MockitoAnnotations.openMocks(this)
         val resources = Mockito.mock(Resources::class.java)