From 5e7af0c5f11ec2e5a273fb44a5a28317fe9951fa Mon Sep 17 00:00:00 2001 From: A117870935 Date: Fri, 28 Apr 2023 01:25:43 +0530 Subject: [PATCH] Customizations done for Sharing with test cases. Download limit functionality added. Email selection from contact implemented with Test Cases. Share new ui updated with divider, index icons, colors, empty state. Received sharing disabled state handled. Test cases updated Updated colors as per NMC-4218 NMC-3928 fix: avoid resetting download counter on every update of advance permission NMC-4653: clear edittext focus on action done --- .../nextcloud/client/FileDisplayActivityIT.kt | 4 +- .../utils/SharePermissionManagerTest.kt | 8 +- .../java/com/nmc/android/ui/FileSharingIT.kt | 395 ++++++++++++ .../nmc/android/ui/RecyclerViewAssertions.kt | 81 +++ .../operations/GetSharesForFileOperationIT.kt | 3 +- .../fragment/FileDetailSharingFragmentIT.kt | 57 +- .../ui/fragment/SharedListFragmentIT.kt | 5 +- app/src/main/AndroidManifest.xml | 3 +- .../nmc/android/utils/CheckableThemeUtils.kt | 117 ++++ .../com/nmc/android/utils/DisplayUtils.kt | 18 + .../com/nmc/android/utils/KeyboardUtils.java | 21 + .../nmc/android/utils/SearchViewThemeUtils.kt | 24 + .../com/nmc/android/utils/TextViewUtils.kt | 47 ++ .../quickPermission/QuickPermissionType.kt | 8 +- .../CreateShareWithShareeOperation.java | 4 +- .../operations/UpdateShareInfoOperation.java | 52 ++ ...leteShareDownloadLimitRemoteOperation.java | 90 +++ .../DownloadLimitResponse.java | 37 ++ .../DownloadLimitXMLParser.java | 323 ++++++++++ .../GetShareDownloadLimitOperation.java | 90 +++ .../ShareDownloadLimitUtils.kt | 30 + ...dateShareDownloadLimitRemoteOperation.java | 114 ++++ .../UsersAndGroupsSearchProvider.java | 35 +- .../android/services/OperationsService.java | 17 + .../android/ui/activity/FileActivity.java | 61 +- .../android/ui/activity/ShareActivity.java | 6 +- .../android/ui/adapter/LinkShareViewHolder.kt | 101 +-- .../android/ui/adapter/ListViewHolder.kt | 1 + .../android/ui/adapter/OCFileListAdapter.java | 3 +- .../android/ui/adapter/OCFileListDelegate.kt | 103 ++- .../adapter/OCFileListGridItemViewHolder.kt | 2 + .../ui/adapter/OCFileListItemViewHolder.kt | 2 + .../OCFileListRecommendedItemViewHolder.kt | 1 + .../ui/adapter/OCFileListViewHolder.kt | 2 + .../adapter/QuickSharingPermissionsAdapter.kt | 9 +- .../android/ui/adapter/ShareViewHolder.java | 61 +- .../android/ui/adapter/ShareeListAdapter.kt | 13 +- .../android/ui/dialog/SendShareDialog.kt | 19 +- .../ui/events/ShareSearchViewFocusEvent.kt | 28 + .../ui/fragment/FileDetailFragment.java | 191 ++---- .../fragment/FileDetailSharingFragment.java | 363 +++++++---- ...ileDetailSharingMenuBottomSheetDialog.java | 22 +- ...eDetailsSharingMenuBottomSheetActions.java | 8 +- .../FileDetailsSharingProcessFragment.kt | 515 ++++++++------- .../ui/fragment/OCFileListFragment.java | 8 +- ...ckSharingPermissionsBottomSheetDialog.java | 30 +- .../fragment/util/SharePermissionManager.kt | 93 ++- .../ui/helpers/FileOperationsHelper.java | 30 +- .../owncloud/android/utils/KeyboardUtils.kt | 10 + .../res/color/share_contact_icon_color.xml | 11 + .../res/drawable-night/ic_internal_share.xml | 12 + app/src/main/res/drawable/ic_calendar.xml | 12 + app/src/main/res/drawable/ic_clipboard.xml | 12 + app/src/main/res/drawable/ic_contact_book.xml | 14 +- .../main/res/drawable/ic_external_share.xml | 12 + .../main/res/drawable/ic_internal_share.xml | 12 + app/src/main/res/drawable/ic_open_in.xml | 12 + app/src/main/res/drawable/ic_pencil_edit.xml | 12 + app/src/main/res/drawable/ic_shared.xml | 12 + .../main/res/drawable/ic_shared_with_me.xml | 13 + app/src/main/res/drawable/ic_sharing_edit.xml | 21 + .../res/drawable/ic_sharing_file_drop.xml | 21 + .../ic_sharing_quick_permission_arrow.xml | 21 + .../res/drawable/ic_sharing_read_only.xml | 21 + app/src/main/res/drawable/share_et_bg.xml | 5 + .../res/drawable/share_search_background.xml | 24 + .../res/drawable/sharing_email_warning_bg.xml | 6 + .../main/res/layout/file_details_fragment.xml | 47 +- .../file_details_share_link_share_item.xml | 152 ++--- ...details_share_public_link_add_new_item.xml | 4 +- .../layout/file_details_share_share_item.xml | 111 ++-- .../layout/file_details_sharing_fragment.xml | 265 ++++---- ...ils_sharing_menu_bottom_sheet_fragment.xml | 74 ++- .../file_details_sharing_process_fragment.xml | 610 ++++++++++++------ .../layout/item_quick_share_permissions.xml | 50 +- app/src/main/res/layout/list_item.xml | 20 +- .../res/layout/quick_permission_layout.xml | 80 +++ .../res/values-de/nmc_sharing_strings.xml | 55 ++ app/src/main/res/values-de/strings.xml | 19 +- app/src/main/res/values-night/colors.xml | 65 ++ app/src/main/res/values-sw480dp/bool.xml | 4 + app/src/main/res/values/bool.xml | 4 + app/src/main/res/values/colors.xml | 90 +++ app/src/main/res/values/dimens.xml | 32 + .../main/res/values/nmc_sharing_strings.xml | 54 ++ .../main/res/values/nmc_sharing_styles.xml | 14 + app/src/main/res/values/strings.xml | 11 +- app/src/main/res/values/styles.xml | 4 +- .../ui/adapter/ShareeListAdapterTest.kt | 4 +- 89 files changed, 4015 insertions(+), 1272 deletions(-) create mode 100644 app/src/androidTest/java/com/nmc/android/ui/FileSharingIT.kt create mode 100644 app/src/androidTest/java/com/nmc/android/ui/RecyclerViewAssertions.kt create mode 100644 app/src/main/java/com/nmc/android/utils/CheckableThemeUtils.kt create mode 100644 app/src/main/java/com/nmc/android/utils/DisplayUtils.kt create mode 100644 app/src/main/java/com/nmc/android/utils/KeyboardUtils.java create mode 100644 app/src/main/java/com/nmc/android/utils/SearchViewThemeUtils.kt create mode 100644 app/src/main/java/com/nmc/android/utils/TextViewUtils.kt create mode 100644 app/src/main/java/com/owncloud/android/operations/share_download_limit/DeleteShareDownloadLimitRemoteOperation.java create mode 100644 app/src/main/java/com/owncloud/android/operations/share_download_limit/DownloadLimitResponse.java create mode 100644 app/src/main/java/com/owncloud/android/operations/share_download_limit/DownloadLimitXMLParser.java create mode 100644 app/src/main/java/com/owncloud/android/operations/share_download_limit/GetShareDownloadLimitOperation.java create mode 100644 app/src/main/java/com/owncloud/android/operations/share_download_limit/ShareDownloadLimitUtils.kt create mode 100644 app/src/main/java/com/owncloud/android/operations/share_download_limit/UpdateShareDownloadLimitRemoteOperation.java create mode 100644 app/src/main/java/com/owncloud/android/ui/events/ShareSearchViewFocusEvent.kt create mode 100644 app/src/main/res/color/share_contact_icon_color.xml create mode 100644 app/src/main/res/drawable-night/ic_internal_share.xml create mode 100644 app/src/main/res/drawable/ic_calendar.xml create mode 100644 app/src/main/res/drawable/ic_clipboard.xml create mode 100644 app/src/main/res/drawable/ic_external_share.xml create mode 100644 app/src/main/res/drawable/ic_internal_share.xml create mode 100644 app/src/main/res/drawable/ic_open_in.xml create mode 100644 app/src/main/res/drawable/ic_pencil_edit.xml create mode 100644 app/src/main/res/drawable/ic_shared.xml create mode 100644 app/src/main/res/drawable/ic_shared_with_me.xml create mode 100644 app/src/main/res/drawable/ic_sharing_edit.xml create mode 100644 app/src/main/res/drawable/ic_sharing_file_drop.xml create mode 100644 app/src/main/res/drawable/ic_sharing_quick_permission_arrow.xml create mode 100644 app/src/main/res/drawable/ic_sharing_read_only.xml create mode 100644 app/src/main/res/drawable/share_et_bg.xml create mode 100644 app/src/main/res/drawable/share_search_background.xml create mode 100644 app/src/main/res/drawable/sharing_email_warning_bg.xml create mode 100644 app/src/main/res/layout/quick_permission_layout.xml create mode 100644 app/src/main/res/values-de/nmc_sharing_strings.xml create mode 100644 app/src/main/res/values-sw480dp/bool.xml create mode 100644 app/src/main/res/values/bool.xml create mode 100644 app/src/main/res/values/dimens.xml create mode 100644 app/src/main/res/values/nmc_sharing_strings.xml create mode 100644 app/src/main/res/values/nmc_sharing_styles.xml diff --git a/app/src/androidTest/java/com/nextcloud/client/FileDisplayActivityIT.kt b/app/src/androidTest/java/com/nextcloud/client/FileDisplayActivityIT.kt index ad6c28f597ed..ed70e88ddcdb 100644 --- a/app/src/androidTest/java/com/nextcloud/client/FileDisplayActivityIT.kt +++ b/app/src/androidTest/java/com/nextcloud/client/FileDisplayActivityIT.kt @@ -40,6 +40,8 @@ import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Before +import com.owncloud.android.ui.fragment.util.SharePermissionManager +import org.junit.Assert import org.junit.Rule import org.junit.Test @@ -74,7 +76,7 @@ class FileDisplayActivityIT : AbstractOnServerIT() { "admin", false, "", - OCShare.MAXIMUM_PERMISSIONS_FOR_FOLDER + SharePermissionManager.CAN_EDIT_PERMISSIONS_FOR_FOLDER ).execute(client).isSuccess ) diff --git a/app/src/androidTest/java/com/nextcloud/utils/SharePermissionManagerTest.kt b/app/src/androidTest/java/com/nextcloud/utils/SharePermissionManagerTest.kt index 3f65ba31269a..1c2016d1096c 100644 --- a/app/src/androidTest/java/com/nextcloud/utils/SharePermissionManagerTest.kt +++ b/app/src/androidTest/java/com/nextcloud/utils/SharePermissionManagerTest.kt @@ -83,7 +83,7 @@ class SharePermissionManagerTest { // region Helper Method Tests @Test fun testCanEditShouldReturnTrueIfAllPermissionsPresent() { - val share = createShare(OCShare.MAXIMUM_PERMISSIONS_FOR_FOLDER, isFolder = true) + val share = createShare(SharePermissionManager.CAN_EDIT_PERMISSIONS_FOR_FOLDER, isFolder = true) assertTrue(SharePermissionManager.canEdit(share)) } @@ -127,7 +127,7 @@ class SharePermissionManagerTest { @Test fun testGetMaximumPermissionForFolder() { assertEquals( - OCShare.MAXIMUM_PERMISSIONS_FOR_FOLDER, + SharePermissionManager.CAN_EDIT_PERMISSIONS_FOR_FOLDER, SharePermissionManager.getMaximumPermission(isFolder = true) ) } @@ -135,7 +135,7 @@ class SharePermissionManagerTest { @Test fun testGetMaximumPermissionForFile() { assertEquals( - OCShare.MAXIMUM_PERMISSIONS_FOR_FILE, + SharePermissionManager.CAN_EDIT_PERMISSIONS_FOR_FILE, SharePermissionManager.getMaximumPermission(isFolder = false) ) } @@ -144,7 +144,7 @@ class SharePermissionManagerTest { // region GetSelectedTypeTests @Test fun testGetSelectedTypeShouldReturnCanEditWhenFullPermissionsGiven() { - val share = createShare(OCShare.MAXIMUM_PERMISSIONS_FOR_FILE) + val share = createShare(SharePermissionManager.CAN_EDIT_PERMISSIONS_FOR_FILE) assertEquals(QuickPermissionType.CAN_EDIT, SharePermissionManager.getSelectedType(share, encrypted = false)) } diff --git a/app/src/androidTest/java/com/nmc/android/ui/FileSharingIT.kt b/app/src/androidTest/java/com/nmc/android/ui/FileSharingIT.kt new file mode 100644 index 000000000000..6761c6ff3e79 --- /dev/null +++ b/app/src/androidTest/java/com/nmc/android/ui/FileSharingIT.kt @@ -0,0 +1,395 @@ +package com.nmc.android.ui + +import androidx.recyclerview.widget.RecyclerView +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.Espresso.pressBack +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.contrib.RecyclerViewActions +import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition +import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.isEnabled +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.rules.ActivityScenarioRule +import com.nextcloud.test.RetryTestRule +import com.nextcloud.test.TestActivity +import com.nmc.android.ui.RecyclerViewAssertions.clickChildViewWithId +import com.nmc.android.ui.RecyclerViewAssertions.withRecyclerView +import com.owncloud.android.AbstractIT +import com.owncloud.android.R +import com.owncloud.android.datamodel.OCFile +import com.owncloud.android.lib.resources.shares.OCShare +import com.owncloud.android.lib.resources.shares.ShareType +import com.owncloud.android.ui.fragment.FileDetailFragment +import com.owncloud.android.ui.fragment.util.SharePermissionManager +import org.hamcrest.Matchers.not +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +class FileSharingIT : AbstractIT() { + @get:Rule + val activityScenarioRule = ActivityScenarioRule(TestActivity::class.java) + + @get:Rule + val retryRule = RetryTestRule() + + lateinit var file: OCFile + lateinit var folder: OCFile + + @Before + fun before() { + activityScenarioRule.scenario.onActivity { + file = OCFile("/test.md").apply { + remoteId = "00000001" + parentId = it.storageManager.getFileByEncryptedRemotePath("/").fileId + permissions = OCFile.PERMISSION_CAN_RESHARE + } + + folder = OCFile("/test").apply { + setFolder() + remoteId = "00000002" + parentId = it.storageManager.getFileByEncryptedRemotePath("/").fileId + permissions = OCFile.PERMISSION_CAN_RESHARE + } + } + } + + private fun show(file: OCFile) { + val fragment = FileDetailFragment.newInstance(file, user, 0) + + activityScenarioRule.scenario.onActivity { + it.addFragment(fragment) + } + + waitForIdleSync() + + shortSleep() + } + + @Test + fun validateUiOfFileDetailFragment() { + show(file) + + onView(withId(R.id.filename)).check(matches(isCompletelyDisplayed())) + onView(withId(R.id.filename)).check(matches(withText("test.md"))) + onView(withId(R.id.favorite)).check(matches(isCompletelyDisplayed())) + onView(withId(R.id.size)).check(matches(isCompletelyDisplayed())) + onView(withId(R.id.file_separator)).check(matches(isCompletelyDisplayed())) + onView(withId(R.id.last_modification_timestamp)).check(matches(isCompletelyDisplayed())) + } + + private fun validateCommonUI() { + onView(withId(R.id.sharing_heading_title)).check(matches(isCompletelyDisplayed())) + onView(withId(R.id.sharing_heading_title)).check(matches(withText("Send link by mail"))) + + onView(withId(R.id.searchView)).check(matches(isCompletelyDisplayed())) + onView(withId(R.id.searchView)).check(matches(isEnabled())) + onView(withId(R.id.pick_contact_email_btn)).check(matches(isCompletelyDisplayed())) + onView(withId(R.id.pick_contact_email_btn)).check(matches(isEnabled())) + + onView(withId(R.id.or_section_layout)).check(matches(isCompletelyDisplayed())) + + onView(withId(R.id.link_share_section_heading)).check(matches(isCompletelyDisplayed())) + onView(withId(R.id.link_share_section_heading)).check(matches(withText("Copy link"))) + + onView(withId(R.id.share_create_new_link)).check(matches(isCompletelyDisplayed())) + onView(withId(R.id.share_create_new_link)).check(matches(withText("Create new link"))) + + onView(withId(R.id.shared_with_divider)).check(matches(isCompletelyDisplayed())) + + onView(withId(R.id.tv_your_shares)).check(matches(isCompletelyDisplayed())) + onView(withId(R.id.tv_your_shares)).check(matches(withText("Shared with"))) + } + + @Test + fun validateUiForEmptyShares() { + show(file) + + validateCommonUI() + + onView(withId(R.id.linkSharesList)).check(matches(not(isDisplayed()))) + + onView(withId(R.id.sharesList)).check(matches(not(isDisplayed()))) + + onView(withId(R.id.tv_empty_shares)).check(matches(isCompletelyDisplayed())) + onView(withId(R.id.tv_empty_shares)).check(matches(withText("You have not yet shared your file/folder. Share to give others access."))) + } + + @Test + fun validateUiForFileWithShares() { + activityScenarioRule.scenario.onActivity { + OCShare(file.decryptedRemotePath).apply { + remoteId = 1 + shareType = ShareType.USER + sharedWithDisplayName = "Admin" + permissions = OCShare.READ_PERMISSION_FLAG + userId = getUserId(user) + it.storageManager.saveShare(this) + } + + OCShare(file.decryptedRemotePath).apply { + remoteId = 3 + shareType = ShareType.EMAIL + permissions = SharePermissionManager.CAN_EDIT_PERMISSIONS_FOR_FILE + sharedWithDisplayName = "johndoe@gmail.com" + userId = getUserId(user) + it.storageManager.saveShare(this) + } + + OCShare(file.decryptedRemotePath).apply { + remoteId = 4 + shareType = ShareType.PUBLIC_LINK + permissions = OCShare.READ_PERMISSION_FLAG + label = "Customer" + it.storageManager.saveShare(this) + } + + OCShare(file.decryptedRemotePath).apply { + remoteId = 5 + shareType = ShareType.PUBLIC_LINK + permissions = SharePermissionManager.CAN_EDIT_PERMISSIONS_FOR_FILE + label = "Colleagues" + it.storageManager.saveShare(this) + } + + } + show(file) + + validateCommonUI() + + onView(withId(R.id.linkSharesList)).check(matches(isCompletelyDisplayed())) + onView(withId(R.id.sharesList)).check(matches(isCompletelyDisplayed())) + onView(withId(R.id.tv_empty_shares)).check(matches(not(isDisplayed()))) + } + + @Test + fun validateUiWithResharingNotAllowed() { + file = file.apply { + permissions = "" + ownerDisplayName = "John Doe" + ownerId = "JohnDoe" + note = "Shared for testing purpose." + } + show(file) + + onView(withId(R.id.tv_resharing_info)).check(matches(isCompletelyDisplayed())) + onView(withId(R.id.tv_resharing_info)).check(matches(withText("This file / folder was shared with you by John Doe"))) + + onView(withId(R.id.tv_resharing_status)).check(matches(isCompletelyDisplayed())) + onView(withId(R.id.tv_resharing_status)).check(matches(withText("Resharing is not allowed."))) + + onView(withId(R.id.searchView)).check(matches(isCompletelyDisplayed())) + onView(withId(R.id.searchView)).check(matches(not(isEnabled()))) + onView(withId(R.id.pick_contact_email_btn)).check(matches(isCompletelyDisplayed())) + onView(withId(R.id.pick_contact_email_btn)).check(matches(not(isEnabled()))) + + onView(withId(R.id.or_section_layout)).check(matches(not(isDisplayed()))) + onView(withId(R.id.link_share_section_heading)).check(matches(not(isDisplayed()))) + onView(withId(R.id.linkSharesList)).check(matches(not(isDisplayed()))) + onView(withId(R.id.share_create_new_link)).check(matches(not(isDisplayed()))) + onView(withId(R.id.shared_with_divider)).check(matches(not(isDisplayed()))) + onView(withId(R.id.tv_your_shares)).check(matches(not(isDisplayed()))) + onView(withId(R.id.sharesList)).check(matches(not(isDisplayed()))) + onView(withId(R.id.tv_empty_shares)).check(matches(not(isDisplayed()))) + } + + @Test + fun validateUiWithResharingAllowed() { + file = file.apply { + ownerDisplayName = "John Doe" + ownerId = "JohnDoe" + } + show(file) + + validateCommonUI() + + onView(withId(R.id.tv_resharing_info)).check(matches(isCompletelyDisplayed())) + onView(withId(R.id.tv_resharing_info)).check(matches(withText("This file / folder was shared with you by John Doe"))) + + onView(withId(R.id.tv_resharing_status)).check(matches(isCompletelyDisplayed())) + onView(withId(R.id.tv_resharing_status)).check(matches(withText("Resharing is allowed."))) + + onView(withId(R.id.linkSharesList)).check(matches(not(isDisplayed()))) + onView(withId(R.id.sharesList)).check(matches(not(isDisplayed()))) + onView(withId(R.id.tv_empty_shares)).check(matches(isCompletelyDisplayed())) + onView(withId(R.id.tv_empty_shares)).check(matches(withText("You have not yet shared your file/folder. Share to give others access."))) + } + + @Test + fun validateQuickPermissionDialogForFiles() { + val sharesList: MutableList = mutableListOf() + + activityScenarioRule.scenario.onActivity { it -> + OCShare(file.decryptedRemotePath).apply { + remoteId = 1 + shareType = ShareType.USER + sharedWithDisplayName = "Admin" + permissions = OCShare.READ_PERMISSION_FLAG + userId = getUserId(user) + isFolder = false + it.storageManager.saveShare(this) + } + + OCShare(file.decryptedRemotePath).apply { + remoteId = 3 + shareType = ShareType.EMAIL + sharedWithDisplayName = "johndoe@gmail.com" + permissions = SharePermissionManager.CAN_EDIT_PERMISSIONS_FOR_FILE + userId = getUserId(user) + isFolder = false + it.storageManager.saveShare(this) + } + + OCShare(file.decryptedRemotePath).apply { + remoteId = 4 + shareType = ShareType.PUBLIC_LINK + permissions = OCShare.READ_PERMISSION_FLAG + userId = getUserId(user) + label = "Customer" + isFolder = false + it.storageManager.saveShare(this) + } + + //get other shares + sharesList.addAll(it.storageManager.getSharesWithForAFile(file.remotePath, user.accountName)) + + //get public link shares + sharesList.addAll(it.storageManager.getSharesByPathAndType(file.remotePath, ShareType.PUBLIC_LINK, "")) + + sharesList.sortByDescending { share -> share.shareType } + } + + + assertEquals(3, sharesList.size) + + show(file) + + for (i in sharesList.indices) { + val share = sharesList[i] + //since for public link the quick permission button is disabled + if (share.shareType == ShareType.PUBLIC_LINK) { + continue + } + showQuickPermissionDialogAndValidate(i, file.isFolder, share) + pressBack() + } + } + + @Test + fun validateQuickPermissionDialogForFolder() { + val sharesList: MutableList = mutableListOf() + + activityScenarioRule.scenario.onActivity { it -> + OCShare(folder.decryptedRemotePath).apply { + remoteId = 1 + shareType = ShareType.USER + sharedWithDisplayName = "Admin" + permissions = OCShare.CREATE_PERMISSION_FLAG + userId = getUserId(user) + isFolder = true + it.storageManager.saveShare(this) + } + + OCShare(folder.decryptedRemotePath).apply { + remoteId = 3 + shareType = ShareType.EMAIL + sharedWithDisplayName = "johndoe@gmail.com" + permissions = SharePermissionManager.CAN_EDIT_PERMISSIONS_FOR_FOLDER + userId = getUserId(user) + isFolder = true + it.storageManager.saveShare(this) + } + + OCShare(folder.decryptedRemotePath).apply { + remoteId = 4 + shareType = ShareType.PUBLIC_LINK + permissions = OCShare.READ_PERMISSION_FLAG + userId = getUserId(user) + label = "Customer" + isFolder = true + it.storageManager.saveShare(this) + } + + //get other shares + sharesList.addAll(it.storageManager.getSharesWithForAFile(folder.remotePath, user.accountName)) + + //get public link shares + sharesList.addAll(it.storageManager.getSharesByPathAndType(folder.remotePath, ShareType.PUBLIC_LINK, "")) + + sharesList.sortByDescending { share -> share.shareType } + } + + + assertEquals(3, sharesList.size) + + show(folder) + + for (i in sharesList.indices) { + val share = sharesList[i] + showQuickPermissionDialogAndValidate(i, folder.isFolder, share) + pressBack() + } + } + + private fun showQuickPermissionDialogAndValidate(index: Int, isFolder: Boolean, ocShare: OCShare) { + onView(withId(R.id.sharesList)).perform( + actionOnItemAtPosition( + index, + clickChildViewWithId(if (ocShare.shareType == ShareType.USER) R.id.share_name_layout else R.id.share_by_link_container) + ) + ) + + val permissionList = permissionList(isFolder, ocShare.shareType!!) + + for (i in permissionList.indices) { + // Scroll to the item at position i + onView(withId(R.id.rv_quick_share_permissions)).perform( + RecyclerViewActions.scrollToPosition( + i + ) + ) + + val permissionTextView = onView( + withRecyclerView(R.id.rv_quick_share_permissions) + .atPositionOnView(i, R.id.tv_quick_share_name) + ) + permissionTextView.check(matches(withText(permissionList[i]))) + + val permissionCheckView = onView( + withRecyclerView(R.id.rv_quick_share_permissions) + .atPositionOnView(i, R.id.tv_quick_share_check_icon) + ) + if ((permissionList[i] == "Read only" && SharePermissionManager.isViewOnly(ocShare)) + || (permissionList[i] == "Can edit" && SharePermissionManager.canEdit(ocShare)) + || (permissionList[i] == "Filedrop only" && SharePermissionManager.isFileRequest(ocShare)) + ) { + permissionCheckView.check(matches(isDisplayed())) + } + } + } + + @After + override fun after() { + activityScenarioRule.scenario.onActivity { + it.storageManager.cleanShares() + it.finish() + } + super.after() + } + + companion object { + private val filePermissionList = listOf("Read only", "Can edit") + private val folderExternalAndLinkSharePermissionList = listOf("Read only", "Can edit", "Filedrop only") + private val folderOtherSharePermissionList = listOf("Read only", "Can edit") + + fun permissionList(isFolder: Boolean, shareType: ShareType): List = + if (isFolder) { + if (shareType == ShareType.PUBLIC_LINK || shareType == ShareType.EMAIL) folderExternalAndLinkSharePermissionList + else folderOtherSharePermissionList + } else filePermissionList + } +} diff --git a/app/src/androidTest/java/com/nmc/android/ui/RecyclerViewAssertions.kt b/app/src/androidTest/java/com/nmc/android/ui/RecyclerViewAssertions.kt new file mode 100644 index 000000000000..040b165f9e82 --- /dev/null +++ b/app/src/androidTest/java/com/nmc/android/ui/RecyclerViewAssertions.kt @@ -0,0 +1,81 @@ +package com.nmc.android.ui + +import android.content.res.Resources +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import androidx.test.espresso.UiController +import androidx.test.espresso.ViewAction +import org.hamcrest.Description +import org.hamcrest.Matcher +import org.hamcrest.TypeSafeMatcher + +object RecyclerViewAssertions { + + fun clickChildViewWithId(id: Int): ViewAction { + return object : ViewAction { + override fun getConstraints(): Matcher? { + return null + } + + override fun getDescription(): String { + return "Click on a child view with specified id." + } + + override fun perform(uiController: UiController?, view: View) { + val v: View = view.findViewById(id) + v.performClick() + } + } + } + + fun withRecyclerView(recyclerViewId: Int): RecyclerViewMatcher { + return RecyclerViewMatcher(recyclerViewId) + } + + class RecyclerViewMatcher(private val recyclerViewId: Int) { + fun atPosition(position: Int): Matcher { + return atPositionOnView(position, -1) + } + + fun atPositionOnView(position: Int, targetViewId: Int): Matcher { + return object : TypeSafeMatcher() { + var resources: Resources? = null + var childView: View? = null + + override fun describeTo(description: Description?) { + var idDescription = recyclerViewId.toString() + resources?.let { + idDescription = try { + resources!!.getResourceName(recyclerViewId) + } catch (exception: Resources.NotFoundException) { + "$recyclerViewId (resource name not found)" + } + } + + description?.appendText("with id: $idDescription") + } + + override fun matchesSafely(view: View?): Boolean { + resources = view?.resources + + if (childView == null) { + val recyclerView = view?.rootView?.findViewById(recyclerViewId) + + if (recyclerView != null && recyclerView.id == recyclerViewId) { + childView = recyclerView.findViewHolderForAdapterPosition(position)?.itemView + } else { + return false + } + } + + return if (targetViewId == -1) { + view == childView + } else { + val targetView = childView?.findViewById(targetViewId) + view == targetView + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/owncloud/android/operations/GetSharesForFileOperationIT.kt b/app/src/androidTest/java/com/owncloud/android/operations/GetSharesForFileOperationIT.kt index 1d268ce743db..b6cacba07c37 100644 --- a/app/src/androidTest/java/com/owncloud/android/operations/GetSharesForFileOperationIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/operations/GetSharesForFileOperationIT.kt @@ -12,6 +12,7 @@ import com.owncloud.android.lib.resources.files.CreateFolderRemoteOperation import com.owncloud.android.lib.resources.shares.CreateShareRemoteOperation import com.owncloud.android.lib.resources.shares.OCShare import com.owncloud.android.lib.resources.shares.ShareType +import com.owncloud.android.ui.fragment.util.SharePermissionManager import junit.framework.TestCase import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue @@ -32,7 +33,7 @@ class GetSharesForFileOperationIT : AbstractOnServerIT() { "admin", false, "", - OCShare.MAXIMUM_PERMISSIONS_FOR_FOLDER + SharePermissionManager.CAN_EDIT_PERMISSIONS_FOR_FOLDER ) .execute(client).isSuccess ) diff --git a/app/src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailSharingFragmentIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailSharingFragmentIT.kt index 7fa76a889add..6c5bbad19082 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailSharingFragmentIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailSharingFragmentIT.kt @@ -34,8 +34,6 @@ import com.owncloud.android.datamodel.OCFile import com.owncloud.android.lib.resources.shares.OCShare import com.owncloud.android.lib.resources.shares.OCShare.Companion.CREATE_PERMISSION_FLAG import com.owncloud.android.lib.resources.shares.OCShare.Companion.DELETE_PERMISSION_FLAG -import com.owncloud.android.lib.resources.shares.OCShare.Companion.MAXIMUM_PERMISSIONS_FOR_FILE -import com.owncloud.android.lib.resources.shares.OCShare.Companion.MAXIMUM_PERMISSIONS_FOR_FOLDER import com.owncloud.android.lib.resources.shares.OCShare.Companion.NO_PERMISSION import com.owncloud.android.lib.resources.shares.OCShare.Companion.READ_PERMISSION_FLAG import com.owncloud.android.lib.resources.shares.OCShare.Companion.SHARE_PERMISSION_FLAG @@ -180,7 +178,7 @@ class FileDetailSharingFragmentIT : AbstractIT() { remoteId = 1 shareType = ShareType.USER sharedWithDisplayName = "Admin" - permissions = MAXIMUM_PERMISSIONS_FOR_FILE + permissions = SharePermissionManager.CAN_EDIT_PERMISSIONS_FOR_FILE userId = getUserId(user) activity.storageManager.saveShare(this) } @@ -189,7 +187,7 @@ class FileDetailSharingFragmentIT : AbstractIT() { remoteId = 2 shareType = ShareType.GROUP sharedWithDisplayName = "Group" - permissions = MAXIMUM_PERMISSIONS_FOR_FILE + permissions = SharePermissionManager.CAN_EDIT_PERMISSIONS_FOR_FILE userId = getUserId(user) activity.storageManager.saveShare(this) } @@ -338,31 +336,33 @@ class FileDetailSharingFragmentIT : AbstractIT() { // validate view shown on screen onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isDisplayed())) onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.file_request_radio_button)).check(matches(isDisplayed())) + onView(ViewMatchers.withId(R.id.file_drop_radio_button)).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_change_name_switch)).check(matches(isDisplayed())) + onView(ViewMatchers.withId(R.id.share_process_allow_resharing_checkbox)).check(matches(not(isDisplayed()))) + onView(ViewMatchers.withId(R.id.share_process_download_limit_switch)).check(matches(not(isDisplayed()))) // read-only onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isChecked())) onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isNotChecked())) - onView(ViewMatchers.withId(R.id.file_request_radio_button)).check(matches(isNotChecked())) + onView(ViewMatchers.withId(R.id.file_drop_radio_button)).check(matches(isNotChecked())) goBack() // upload and editing - publicShare.permissions = MAXIMUM_PERMISSIONS_FOR_FOLDER + publicShare.permissions = SharePermissionManager.CAN_EDIT_PERMISSIONS_FOR_FOLDER openAdvancedPermissions(sut, publicShare) onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isNotChecked())) onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isChecked())) - onView(ViewMatchers.withId(R.id.file_request_radio_button)).check(matches(isNotChecked())) + onView(ViewMatchers.withId(R.id.file_drop_radio_button)).check(matches(isNotChecked())) goBack() - // file request + // file drop publicShare.permissions = 4 openAdvancedPermissions(sut, publicShare) onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isNotChecked())) onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isNotChecked())) - onView(ViewMatchers.withId(R.id.file_request_radio_button)).check(matches(isChecked())) + onView(ViewMatchers.withId(R.id.file_drop_radio_button)).check(matches(isChecked())) goBack() // password protection @@ -378,7 +378,7 @@ class FileDetailSharingFragmentIT : AbstractIT() { // hide download publicShare.isHideFileDownload = true - publicShare.permissions = MAXIMUM_PERMISSIONS_FOR_FOLDER + publicShare.permissions = SharePermissionManager.CAN_EDIT_PERMISSIONS_FOR_FOLDER openAdvancedPermissions(sut, publicShare) onView(ViewMatchers.withId(R.id.share_process_hide_download_checkbox)).check(matches(isChecked())) goBack() @@ -484,12 +484,12 @@ class FileDetailSharingFragmentIT : AbstractIT() { // validate view shown on screen onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isDisplayed())) onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isDisplayed())) - onView( - ViewMatchers.withId(R.id.file_request_radio_button) - ).check(matches(not(isDisplayed()))) + onView(ViewMatchers.withId(R.id.file_drop_radio_button)).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_change_name_switch)).check(matches(isDisplayed())) + onView(ViewMatchers.withId(R.id.share_process_allow_resharing_checkbox)).check(matches(not(isDisplayed()))) + onView(ViewMatchers.withId(R.id.share_process_download_limit_switch)).check(matches(isDisplayed())) // read-only publicShare.permissions = 17 // from server @@ -498,7 +498,7 @@ class FileDetailSharingFragmentIT : AbstractIT() { goBack() // editing - publicShare.permissions = MAXIMUM_PERMISSIONS_FOR_FILE // from server + publicShare.permissions = SharePermissionManager.CAN_EDIT_PERMISSIONS_FOR_FILE // from server openAdvancedPermissions(sut, publicShare) onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isNotChecked())) onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isChecked())) @@ -615,7 +615,7 @@ class FileDetailSharingFragmentIT : AbstractIT() { onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isDisplayed())) onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isDisplayed())) onView( - ViewMatchers.withId(R.id.file_request_radio_button) + ViewMatchers.withId(R.id.file_drop_radio_button) ).check(matches(not(isDisplayed()))) onView( ViewMatchers.withId(R.id.share_process_hide_download_checkbox) @@ -626,6 +626,8 @@ class FileDetailSharingFragmentIT : AbstractIT() { 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())) + onView(ViewMatchers.withId(R.id.share_process_download_limit_switch)).check(matches(not(isDisplayed()))) // read-only userShare.permissions = 17 // from server @@ -634,7 +636,7 @@ class FileDetailSharingFragmentIT : AbstractIT() { goBack() // editing - userShare.permissions = MAXIMUM_PERMISSIONS_FOR_FILE // from server + userShare.permissions = SharePermissionManager.CAN_EDIT_PERMISSIONS_FOR_FILE // from server openAdvancedPermissions(sut, userShare) onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isNotChecked())) onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isChecked())) @@ -747,7 +749,7 @@ class FileDetailSharingFragmentIT : AbstractIT() { // validate view shown on screen onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isDisplayed())) onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.file_request_radio_button)).check(matches(isDisplayed())) + onView(ViewMatchers.withId(R.id.file_drop_radio_button)).check(matches(isDisplayed())) onView( ViewMatchers.withId(R.id.share_process_hide_download_checkbox) ).check(matches(not(isDisplayed()))) @@ -757,20 +759,21 @@ class FileDetailSharingFragmentIT : AbstractIT() { onView( ViewMatchers.withId(R.id.share_process_change_name_switch) ).check(matches(not(isDisplayed()))) + onView(ViewMatchers.withId(R.id.share_process_download_limit_switch)).check(matches(not(isDisplayed()))) // read-only userShare.permissions = 17 // from server onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isChecked())) onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isNotChecked())) - onView(ViewMatchers.withId(R.id.file_request_radio_button)).check(matches(isNotChecked())) + onView(ViewMatchers.withId(R.id.file_drop_radio_button)).check(matches(isNotChecked())) goBack() // allow upload & editing - userShare.permissions = MAXIMUM_PERMISSIONS_FOR_FOLDER // from server + userShare.permissions = SharePermissionManager.CAN_EDIT_PERMISSIONS_FOR_FOLDER // from server openAdvancedPermissions(sut, userShare) onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isNotChecked())) onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isChecked())) - onView(ViewMatchers.withId(R.id.file_request_radio_button)).check(matches(isNotChecked())) + onView(ViewMatchers.withId(R.id.file_drop_radio_button)).check(matches(isNotChecked())) goBack() // file request @@ -778,7 +781,7 @@ class FileDetailSharingFragmentIT : AbstractIT() { openAdvancedPermissions(sut, userShare) onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isNotChecked())) onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isNotChecked())) - onView(ViewMatchers.withId(R.id.file_request_radio_button)).check(matches(isChecked())) + onView(ViewMatchers.withId(R.id.file_drop_radio_button)).check(matches(isChecked())) goBack() // set expiration date @@ -884,7 +887,7 @@ class FileDetailSharingFragmentIT : AbstractIT() { @Test fun testUploadAndEditingSharePermissions() { val testCases = mapOf( - MAXIMUM_PERMISSIONS_FOR_FOLDER to true, + SharePermissionManager.CAN_EDIT_PERMISSIONS_FOR_FOLDER to true, NO_PERMISSION to false, READ_PERMISSION_FLAG to false, CREATE_PERMISSION_FLAG to false, @@ -907,8 +910,8 @@ class FileDetailSharingFragmentIT : AbstractIT() { CREATE_PERMISSION_FLAG to false, DELETE_PERMISSION_FLAG to false, SHARE_PERMISSION_FLAG to false, - MAXIMUM_PERMISSIONS_FOR_FOLDER to false, - MAXIMUM_PERMISSIONS_FOR_FILE to false + SharePermissionManager.CAN_EDIT_PERMISSIONS_FOR_FOLDER to false, + SharePermissionManager.CAN_EDIT_PERMISSIONS_FOR_FILE to false ) val share = OCShare() @@ -926,8 +929,8 @@ class FileDetailSharingFragmentIT : AbstractIT() { READ_PERMISSION_FLAG to false, DELETE_PERMISSION_FLAG to false, SHARE_PERMISSION_FLAG to false, - MAXIMUM_PERMISSIONS_FOR_FOLDER to false, - MAXIMUM_PERMISSIONS_FOR_FILE to false + SharePermissionManager.CAN_EDIT_PERMISSIONS_FOR_FOLDER to false, + SharePermissionManager.CAN_EDIT_PERMISSIONS_FOR_FILE to false ) val share = OCShare().apply { diff --git a/app/src/androidTest/java/com/owncloud/android/ui/fragment/SharedListFragmentIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/fragment/SharedListFragmentIT.kt index 9eabce775330..c2a55efafe7f 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/fragment/SharedListFragmentIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/fragment/SharedListFragmentIT.kt @@ -22,6 +22,7 @@ import com.owncloud.android.datamodel.OCFile import com.owncloud.android.lib.resources.shares.OCShare import com.owncloud.android.lib.resources.shares.ShareType import com.owncloud.android.utils.EspressoIdlingResource +import com.owncloud.android.ui.fragment.util.SharePermissionManager import com.owncloud.android.utils.ScreenshotTest import org.junit.After import org.junit.Before @@ -111,7 +112,7 @@ internal class SharedListFragmentIT : AbstractIT() { remoteId = 1 shareType = ShareType.USER sharedWithDisplayName = "Admin" - permissions = OCShare.MAXIMUM_PERMISSIONS_FOR_FILE + permissions = SharePermissionManager.CAN_EDIT_PERMISSIONS_FOR_FILE userId = getUserId(user) sharedDate = 1188206955 mimetype = "image/png" @@ -122,7 +123,7 @@ internal class SharedListFragmentIT : AbstractIT() { remoteId = 2 shareType = ShareType.GROUP sharedWithDisplayName = "Group" - permissions = OCShare.MAXIMUM_PERMISSIONS_FOR_FILE + permissions = SharePermissionManager.CAN_EDIT_PERMISSIONS_FOR_FILE userId = getUserId(user) sharedDate = 1188206955 mimetype = "image/png" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7d0bf6464568..e8854940f39e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -604,7 +604,8 @@ android:label="@string/share_dialog_title" android:launchMode="singleTop" android:theme="@style/Theme.ownCloud.Dialog.NoTitle" - android:windowSoftInputMode="adjustResize"> + android:configChanges="orientation|screenSize" + android:windowSoftInputMode="adjustPan"> diff --git a/app/src/main/java/com/nmc/android/utils/CheckableThemeUtils.kt b/app/src/main/java/com/nmc/android/utils/CheckableThemeUtils.kt new file mode 100644 index 000000000000..a3b8a1149948 --- /dev/null +++ b/app/src/main/java/com/nmc/android/utils/CheckableThemeUtils.kt @@ -0,0 +1,117 @@ +package com.nmc.android.utils + +import android.content.res.ColorStateList +import androidx.appcompat.widget.AppCompatCheckBox +import androidx.appcompat.widget.SwitchCompat +import androidx.core.content.res.ResourcesCompat +import com.owncloud.android.R + +object CheckableThemeUtils { + @JvmStatic + fun tintCheckbox(vararg checkBoxes: AppCompatCheckBox) { + for (checkBox in checkBoxes) { + val checkEnabled = ResourcesCompat.getColor( + checkBox.context.resources, + R.color.checkbox_checked_enabled, + checkBox.context.theme + ) + val checkDisabled = ResourcesCompat.getColor( + checkBox.context.resources, + R.color.checkbox_checked_disabled, + checkBox.context.theme + ) + val uncheckEnabled = ResourcesCompat.getColor( + checkBox.context.resources, + R.color.checkbox_unchecked_enabled, + checkBox.context.theme + ) + val uncheckDisabled = ResourcesCompat.getColor( + checkBox.context.resources, + R.color.checkbox_unchecked_disabled, + checkBox.context.theme + ) + + val states = arrayOf( + intArrayOf(android.R.attr.state_enabled, android.R.attr.state_checked), + intArrayOf(-android.R.attr.state_enabled, android.R.attr.state_checked), + intArrayOf(android.R.attr.state_enabled, -android.R.attr.state_checked), + intArrayOf(-android.R.attr.state_enabled, -android.R.attr.state_checked) + ) + val colors = intArrayOf( + checkEnabled, + checkDisabled, + uncheckEnabled, + uncheckDisabled + ) + checkBox.buttonTintList = ColorStateList(states, colors) + } + } + + @JvmStatic + @JvmOverloads + fun tintSwitch(switchView: SwitchCompat, color: Int = 0, colorText: Boolean = false) { + if (colorText) { + switchView.setTextColor(color) + } + + val states = arrayOf( + intArrayOf(android.R.attr.state_enabled, android.R.attr.state_checked), + intArrayOf(android.R.attr.state_enabled, -android.R.attr.state_checked), + intArrayOf(-android.R.attr.state_enabled) + ) + + val thumbColorCheckedEnabled = ResourcesCompat.getColor( + switchView.context.resources, + R.color.switch_thumb_checked_enabled, + switchView.context.theme + ) + val thumbColorUncheckedEnabled = + ResourcesCompat.getColor( + switchView.context.resources, + R.color.switch_thumb_unchecked_enabled, + switchView.context.theme + ) + val thumbColorDisabled = + ResourcesCompat.getColor( + switchView.context.resources, + R.color.switch_thumb_disabled, + switchView.context.theme + ) + + val thumbColors = intArrayOf( + thumbColorCheckedEnabled, + thumbColorUncheckedEnabled, + thumbColorDisabled + ) + val thumbColorStateList = ColorStateList(states, thumbColors) + + val trackColorCheckedEnabled = ResourcesCompat.getColor( + switchView.context.resources, + R.color.switch_track_checked_enabled, + switchView.context.theme + ) + val trackColorUncheckedEnabled = + ResourcesCompat.getColor( + switchView.context.resources, + R.color.switch_track_unchecked_enabled, + switchView.context.theme + ) + val trackColorDisabled = + ResourcesCompat.getColor( + switchView.context.resources, + R.color.switch_track_disabled, + switchView.context.theme + ) + + val trackColors = intArrayOf( + trackColorCheckedEnabled, + trackColorUncheckedEnabled, + trackColorDisabled + ) + + val trackColorStateList = ColorStateList(states, trackColors) + + switchView.thumbTintList = thumbColorStateList + switchView.trackTintList = trackColorStateList + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nmc/android/utils/DisplayUtils.kt b/app/src/main/java/com/nmc/android/utils/DisplayUtils.kt new file mode 100644 index 000000000000..f58e93c4252a --- /dev/null +++ b/app/src/main/java/com/nmc/android/utils/DisplayUtils.kt @@ -0,0 +1,18 @@ +package com.nmc.android.utils + +import android.content.res.Configuration +import com.owncloud.android.MainApp +import com.owncloud.android.R + +object DisplayUtils { + + @JvmStatic + fun isShowDividerForList(): Boolean = isTablet() || isLandscapeOrientation() + + @JvmStatic + fun isTablet(): Boolean = MainApp.getAppContext().resources.getBoolean(R.bool.isTablet) + + @JvmStatic + fun isLandscapeOrientation(): Boolean = + MainApp.getAppContext().resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE +} \ No newline at end of file diff --git a/app/src/main/java/com/nmc/android/utils/KeyboardUtils.java b/app/src/main/java/com/nmc/android/utils/KeyboardUtils.java new file mode 100644 index 000000000000..ec43ac2dd3a8 --- /dev/null +++ b/app/src/main/java/com/nmc/android/utils/KeyboardUtils.java @@ -0,0 +1,21 @@ +package com.nmc.android.utils; + +import android.app.Activity; +import android.content.Context; +import android.view.View; +import android.view.inputmethod.InputMethodManager; + +public class KeyboardUtils { + + public static void showSoftKeyboard(Context context, View view) { + view.requestFocus(); + InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT); + } + + public static void hideKeyboardFrom(Context context, View view) { + view.clearFocus(); + InputMethodManager imm = (InputMethodManager) context.getSystemService(Activity.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nmc/android/utils/SearchViewThemeUtils.kt b/app/src/main/java/com/nmc/android/utils/SearchViewThemeUtils.kt new file mode 100644 index 000000000000..c7c4a13b6773 --- /dev/null +++ b/app/src/main/java/com/nmc/android/utils/SearchViewThemeUtils.kt @@ -0,0 +1,24 @@ +package com.nmc.android.utils + +import android.content.Context +import android.widget.ImageView +import androidx.appcompat.widget.AppCompatAutoCompleteTextView +import androidx.appcompat.widget.SearchView +import com.owncloud.android.R + +object SearchViewThemeUtils { + fun themeSearchView(context: Context, searchView: SearchView) { + val fontColor = context.resources.getColor(R.color.fontAppbar, null) + val editText: AppCompatAutoCompleteTextView = + searchView.findViewById(androidx.appcompat.R.id.search_src_text) + editText.textSize = 16F + editText.setTextColor(fontColor) + editText.highlightColor = context.resources.getColor(R.color.et_highlight_color, null) + editText.setHintTextColor(context.resources.getColor(R.color.fontSecondaryAppbar, null)) + val closeButton: ImageView = searchView.findViewById(androidx.appcompat.R.id.search_close_btn) + closeButton.setColorFilter(fontColor) + val searchButton: ImageView = searchView.findViewById(androidx.appcompat.R.id.search_button) + searchButton.setImageResource(R.drawable.ic_search) + searchButton.setColorFilter(fontColor) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nmc/android/utils/TextViewUtils.kt b/app/src/main/java/com/nmc/android/utils/TextViewUtils.kt new file mode 100644 index 000000000000..6b81001676a6 --- /dev/null +++ b/app/src/main/java/com/nmc/android/utils/TextViewUtils.kt @@ -0,0 +1,47 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2025 Your Name + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nmc.android.utils + +import android.content.res.Resources +import android.text.Layout +import android.util.DisplayMetrics +import android.view.ViewTreeObserver +import android.widget.TextView + +interface EllipsizeListener { + fun onResult(isEllipsized: Boolean) +} + +object TextViewUtils { + + @JvmStatic + fun isTextEllipsized(textView: TextView, listener: EllipsizeListener) { + // check for devices density smaller than 320dpi + // NMC-4347 fix for smaller devices + if (Resources.getSystem().displayMetrics.densityDpi <= DisplayMetrics.DENSITY_XHIGH) { + listener.onResult(true) + return + } + + textView.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { + override fun onGlobalLayout() { + textView.viewTreeObserver.removeOnGlobalLayoutListener(this) + + val layout: Layout? = textView.layout + val isEllipsized = layout?.let { + for (i in 0 until it.lineCount) { + if (it.getEllipsisCount(i) > 0) return@let true + } + false + } ?: false + + listener.onResult(isEllipsized) + } + }) + } +} diff --git a/app/src/main/java/com/owncloud/android/datamodel/quickPermission/QuickPermissionType.kt b/app/src/main/java/com/owncloud/android/datamodel/quickPermission/QuickPermissionType.kt index a09aa0673e0a..f7381aa3b514 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/quickPermission/QuickPermissionType.kt +++ b/app/src/main/java/com/owncloud/android/datamodel/quickPermission/QuickPermissionType.kt @@ -15,9 +15,9 @@ import com.owncloud.android.lib.resources.shares.OCShare enum class QuickPermissionType(val iconId: Int, val textId: Int) { NONE(R.drawable.ic_unknown, R.string.unknown), - VIEW_ONLY(R.drawable.ic_eye, R.string.share_permission_view_only), + VIEW_ONLY(R.drawable.ic_eye, R.string.share_permission_read_only), CAN_EDIT(R.drawable.ic_edit, R.string.share_permission_can_edit), - FILE_REQUEST(R.drawable.ic_file_request, R.string.share_permission_file_request), + FILE_REQUEST(R.drawable.ic_file_request, R.string.share_permission_file_drop), SECURE_FILE_DROP(R.drawable.ic_file_request, R.string.share_permission_secure_file_drop), CUSTOM_PERMISSIONS(R.drawable.ic_custom_permissions, R.string.share_custom_permission); @@ -38,7 +38,9 @@ enum class QuickPermissionType(val iconId: Int, val textId: Int) { } fun getAvailablePermissions(hasFileRequestPermission: Boolean): List { - val permissions = listOf(VIEW_ONLY, CAN_EDIT, FILE_REQUEST, CUSTOM_PERMISSIONS) + // NMC Customization: not required Custom Permissions + // File Request is File Drop + val permissions = listOf(VIEW_ONLY, CAN_EDIT, FILE_REQUEST) val result = if (hasFileRequestPermission) permissions else permissions.filter { it != FILE_REQUEST } return result.map { type -> diff --git a/app/src/main/java/com/owncloud/android/operations/CreateShareWithShareeOperation.java b/app/src/main/java/com/owncloud/android/operations/CreateShareWithShareeOperation.java index 28e732052c72..8c9dc4e3b2f5 100644 --- a/app/src/main/java/com/owncloud/android/operations/CreateShareWithShareeOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/CreateShareWithShareeOperation.java @@ -233,7 +233,9 @@ protected RemoteOperationResult run(OwnCloudClient client) { // once creating share link update other information UpdateShareInfoOperation updateShareInfoOperation = new UpdateShareInfoOperation(share, getStorageManager()); - updateShareInfoOperation.setExpirationDateInMillis(expirationDateInMillis); + if (expirationDateInMillis > 0) { + updateShareInfoOperation.setExpirationDateInMillis(expirationDateInMillis); + } updateShareInfoOperation.setHideFileDownload(hideFileDownload); updateShareInfoOperation.setNote(noteMessage); updateShareInfoOperation.setLabel(label); diff --git a/app/src/main/java/com/owncloud/android/operations/UpdateShareInfoOperation.java b/app/src/main/java/com/owncloud/android/operations/UpdateShareInfoOperation.java index 3d219893f10f..4147159700d4 100644 --- a/app/src/main/java/com/owncloud/android/operations/UpdateShareInfoOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/UpdateShareInfoOperation.java @@ -19,8 +19,11 @@ import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.lib.resources.shares.GetShareRemoteOperation; import com.owncloud.android.lib.resources.shares.OCShare; +import com.owncloud.android.lib.resources.shares.ShareType; import com.owncloud.android.lib.resources.shares.UpdateShareRemoteOperation; import com.owncloud.android.operations.common.SyncOperation; +import com.owncloud.android.operations.share_download_limit.DeleteShareDownloadLimitRemoteOperation; +import com.owncloud.android.operations.share_download_limit.UpdateShareDownloadLimitRemoteOperation; /** @@ -38,6 +41,8 @@ public class UpdateShareInfoOperation extends SyncOperation { private String password; private String label; private String attributes; + //download limit for link share + private long downloadLimit; private static final String TAG = "UpdateShareInfoOperation"; @@ -123,6 +128,9 @@ protected RemoteOperationResult run(OwnCloudClient client) { ocShare.setPasswordProtected(!TextUtils.isEmpty(password)); ocShare.setRemoteId(shareRemoteId); ocShare.setId(shareId); + + executeShareDownloadLimitOperation(client, ocShare); + getStorageManager().saveShare(ocShare); } } @@ -130,6 +138,46 @@ protected RemoteOperationResult run(OwnCloudClient client) { return result; } + /** + * method will be used to update or delete the download limit for the particular share + * + * @param client + * @param ocShare share object + */ + private void executeShareDownloadLimitOperation(OwnCloudClient client, OCShare ocShare) { + // if share type is of Link Share then only we have to update the download limit if configured by user + // if download limit is -1 means current and previous download limit is same then skip updating download limit + // updating download limit everytime resets the download counter + if (ocShare.getShareType() == ShareType.PUBLIC_LINK && !ocShare.isFolder() && downloadLimit != -1) { + + //if download limit it greater than 0 then update the limit + //else delete the download limit + if (downloadLimit > 0) { + //api will update the download limit for the particular share + UpdateShareDownloadLimitRemoteOperation updateShareDownloadLimitRemoteOperation = + new UpdateShareDownloadLimitRemoteOperation(ocShare.getToken(), downloadLimit); + + RemoteOperationResult downloadLimitOp = + updateShareDownloadLimitRemoteOperation.execute(client); + if (downloadLimitOp.isSuccess()) { + Log_OC.d(TAG, "Download limit updated for the share."); + Log_OC.d(TAG, "Download limit " + downloadLimit); + } + } else { + //api will delete the download limit for the particular share + DeleteShareDownloadLimitRemoteOperation limitRemoteOperation = + new DeleteShareDownloadLimitRemoteOperation(ocShare.getToken()); + + RemoteOperationResult deleteDownloadLimitOp = + limitRemoteOperation.execute(client); + if (deleteDownloadLimitOp.isSuccess()) { + Log_OC.d(TAG, "Download limit delete for the share."); + } + } + + } + } + public void setExpirationDateInMillis(long expirationDateInMillis) { this.expirationDateInMillis = expirationDateInMillis; } @@ -157,5 +205,9 @@ public void setPassword(String password) { public void setLabel(String label) { this.label = label; } + + public void setDownloadLimit(long downloadLimit) { + this.downloadLimit = downloadLimit; + } } diff --git a/app/src/main/java/com/owncloud/android/operations/share_download_limit/DeleteShareDownloadLimitRemoteOperation.java b/app/src/main/java/com/owncloud/android/operations/share_download_limit/DeleteShareDownloadLimitRemoteOperation.java new file mode 100644 index 000000000000..303042240c60 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/operations/share_download_limit/DeleteShareDownloadLimitRemoteOperation.java @@ -0,0 +1,90 @@ +/** + * ownCloud Android client application + * + * @author TSI-mc 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, as published by the Free Software Foundation. + *

+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + *

+ * You should have received a copy of the GNU General Public License along with this program. If not, see + * . + */ + +package com.owncloud.android.operations.share_download_limit; + +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.common.utils.Log_OC; + +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.methods.DeleteMethod; + +/** + * class to delete the download limit for the link share + * this has to be executed when user has toggled off the download limit + *

+ * API : //DELETE to /ocs/v2.php/apps/files_downloadlimit/{share_token}/limit + */ +public class DeleteShareDownloadLimitRemoteOperation extends RemoteOperation { + + private static final String TAG = DeleteShareDownloadLimitRemoteOperation.class.getSimpleName(); + + private final String shareToken; + + public DeleteShareDownloadLimitRemoteOperation(String shareToken) { + this.shareToken = shareToken; + } + + @Override + protected RemoteOperationResult run(OwnCloudClient client) { + RemoteOperationResult result; + int status; + + DeleteMethod deleteMethod = null; + + try { + // Post Method + deleteMethod = new DeleteMethod(client.getBaseUri() + ShareDownloadLimitUtils.INSTANCE.getDownloadLimitApiPath(shareToken)); + + deleteMethod.addRequestHeader(OCS_API_HEADER, OCS_API_HEADER_VALUE); + + status = client.executeMethod(deleteMethod); + + if (isSuccess(status)) { + String response = deleteMethod.getResponseBodyAsString(); + + Log_OC.d(TAG, "Delete Download Limit response: " + response); + + DownloadLimitXMLParser parser = new DownloadLimitXMLParser(); + result = parser.parse(true, response); + + if (result.isSuccess()) { + return result; + } + + } else { + result = new RemoteOperationResult<>(false, deleteMethod); + } + + } catch (Exception e) { + result = new RemoteOperationResult<>(e); + Log_OC.e(TAG, "Exception while deleting share download limit", e); + + } finally { + if (deleteMethod != null) { + deleteMethod.releaseConnection(); + } + } + return result; + } + + private boolean isSuccess(int status) { + return status == HttpStatus.SC_OK || status == HttpStatus.SC_BAD_REQUEST; + } + +} diff --git a/app/src/main/java/com/owncloud/android/operations/share_download_limit/DownloadLimitResponse.java b/app/src/main/java/com/owncloud/android/operations/share_download_limit/DownloadLimitResponse.java new file mode 100644 index 000000000000..f54edb17fc60 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/operations/share_download_limit/DownloadLimitResponse.java @@ -0,0 +1,37 @@ +package com.owncloud.android.operations.share_download_limit; + +/** + * response from the Get download limit api + * + * + * + * ok + * 200 + * OK + * + * + * 5 + * 0 + * + * + */ +public class DownloadLimitResponse { + private long limit; + private long count; + + public long getLimit() { + return limit; + } + + public void setLimit(long limit) { + this.limit = limit; + } + + public long getCount() { + return count; + } + + public void setCount(long count) { + this.count = count; + } +} diff --git a/app/src/main/java/com/owncloud/android/operations/share_download_limit/DownloadLimitXMLParser.java b/app/src/main/java/com/owncloud/android/operations/share_download_limit/DownloadLimitXMLParser.java new file mode 100644 index 000000000000..07121f52e110 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/operations/share_download_limit/DownloadLimitXMLParser.java @@ -0,0 +1,323 @@ +package com.owncloud.android.operations.share_download_limit; + +import android.util.Xml; + +import com.owncloud.android.lib.common.operations.RemoteOperationResult; +import com.owncloud.android.lib.common.utils.Log_OC; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * class to parse the Download Limit api XML response This class code referenced from java in NC library + */ +public class DownloadLimitXMLParser { + private static final String TAG = DownloadLimitXMLParser.class.getSimpleName(); + + // No namespaces + private static final String ns = null; + + // NODES for XML Parser + private static final String NODE_OCS = "ocs"; + + private static final String NODE_META = "meta"; + private static final String NODE_STATUS = "status"; + private static final String NODE_STATUS_CODE = "statuscode"; + private static final String NODE_MESSAGE = "message"; + + private static final String NODE_DATA = "data"; + private static final String NODE_LIMIT = "limit"; + private static final String NODE_COUNT = "count"; + + private static final int SUCCESS = 100; + private static final int OK = 200; + private static final int ERROR_WRONG_PARAMETER = 400; + private static final int ERROR_FORBIDDEN = 403; + private static final int ERROR_NOT_FOUND = 404; + + private String mStatus; + private int mStatusCode; + private String mMessage = ""; + + // Getters and Setters + public String getStatus() { + return mStatus; + } + + public void setStatus(String status) { + this.mStatus = status; + } + + public int getStatusCode() { + return mStatusCode; + } + + public void setStatusCode(int statusCode) { + this.mStatusCode = statusCode; + } + + public String getMessage() { + return mMessage; + } + + public boolean isSuccess() { + return mStatusCode == SUCCESS || mStatusCode == OK; + } + + public boolean isForbidden() { + return mStatusCode == ERROR_FORBIDDEN; + } + + public boolean isNotFound() { + return mStatusCode == ERROR_NOT_FOUND; + } + + public boolean isWrongParameter() { + return mStatusCode == ERROR_WRONG_PARAMETER; + } + + public void setMessage(String message) { + this.mMessage = message; + } + + /** + * method to parse the Download limit response + * @param isGet check if parsing has to do for GET api or not + * because the parsing will depend on that + * if API is GET then response will have tag else it wont have + * @param serverResponse + * @return + */ + public RemoteOperationResult parse(boolean isGet, String serverResponse) { + if (serverResponse == null || serverResponse.length() == 0) { + return new RemoteOperationResult<>(RemoteOperationResult.ResultCode.WRONG_SERVER_RESPONSE); + } + + RemoteOperationResult result; + try { + // Parse xml response and obtain the list of downloadLimitResponse + InputStream is = new ByteArrayInputStream(serverResponse.getBytes()); + + DownloadLimitResponse downloadLimitResponse = parseXMLResponse(is); + + if (isSuccess()) { + if (downloadLimitResponse != null && isGet) { + result = new RemoteOperationResult<>(RemoteOperationResult.ResultCode.OK); + result.setResultData(downloadLimitResponse); + } else if (!isGet) { + result = new RemoteOperationResult<>(RemoteOperationResult.ResultCode.OK); + } else { + result = new RemoteOperationResult<>(RemoteOperationResult.ResultCode.WRONG_SERVER_RESPONSE); + Log_OC.e(TAG, "Successful status with no share in the response"); + } + } else if (isWrongParameter()) { + result = new RemoteOperationResult<>(RemoteOperationResult.ResultCode.SHARE_WRONG_PARAMETER); + result.setMessage(getMessage()); + } else if (isNotFound()) { + result = new RemoteOperationResult<>(RemoteOperationResult.ResultCode.SHARE_NOT_FOUND); + result.setMessage(getMessage()); + } else if (isForbidden()) { + result = new RemoteOperationResult<>(RemoteOperationResult.ResultCode.SHARE_FORBIDDEN); + result.setMessage(getMessage()); + } else { + result = new RemoteOperationResult<>(RemoteOperationResult.ResultCode.WRONG_SERVER_RESPONSE); + result.setMessage(getMessage()); + } + + } catch (XmlPullParserException e) { + Log_OC.e(TAG, "Error parsing response from server ", e); + result = new RemoteOperationResult<>(RemoteOperationResult.ResultCode.WRONG_SERVER_RESPONSE); + + } catch (IOException e) { + Log_OC.e(TAG, "Error reading response from server ", e); + result = new RemoteOperationResult<>(RemoteOperationResult.ResultCode.WRONG_SERVER_RESPONSE); + } + + return result; + } + + /** + * Parse is as response of Share API + * + * @param is InputStream to parse + * @return List of ShareRemoteFiles + * @throws XmlPullParserException + * @throws IOException + */ + private DownloadLimitResponse parseXMLResponse(InputStream is) throws XmlPullParserException, IOException { + try { + // XMLPullParser + XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); + factory.setNamespaceAware(true); + + XmlPullParser parser = Xml.newPullParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); + parser.setInput(is, null); + parser.nextTag(); + return readOCS(parser); + + } finally { + is.close(); + } + } + + /** + * Parse OCS node + * + * @param parser + * @return List of ShareRemoteFiles + * @throws XmlPullParserException + * @throws IOException + */ + private DownloadLimitResponse readOCS(XmlPullParser parser) throws XmlPullParserException, + IOException { + DownloadLimitResponse downloadLimitResponse = new DownloadLimitResponse(); + parser.require(XmlPullParser.START_TAG, ns, NODE_OCS); + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) { + continue; + } + String name = parser.getName(); + // read NODE_META and NODE_DATA + if (NODE_META.equalsIgnoreCase(name)) { + readMeta(parser); + } else if (NODE_DATA.equalsIgnoreCase(name)) { + downloadLimitResponse = readData(parser); + } else { + skip(parser); + } + + } + return downloadLimitResponse; + + + } + + /** + * Parse Meta node + * + * @param parser + * @throws XmlPullParserException + * @throws IOException + */ + private void readMeta(XmlPullParser parser) throws XmlPullParserException, IOException { + parser.require(XmlPullParser.START_TAG, ns, NODE_META); + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) { + continue; + } + String name = parser.getName(); + + if (NODE_STATUS.equalsIgnoreCase(name)) { + setStatus(readNode(parser, NODE_STATUS)); + + } else if (NODE_STATUS_CODE.equalsIgnoreCase(name)) { + setStatusCode(Integer.parseInt(readNode(parser, NODE_STATUS_CODE))); + + } else if (NODE_MESSAGE.equalsIgnoreCase(name)) { + setMessage(readNode(parser, NODE_MESSAGE)); + + } else { + skip(parser); + } + + } + } + + /** + * Parse Data node + * + * @param parser + * @return + * @throws XmlPullParserException + * @throws IOException + */ + private DownloadLimitResponse readData(XmlPullParser parser) throws XmlPullParserException, + IOException { + DownloadLimitResponse downloadLimitResponse = new DownloadLimitResponse(); + + parser.require(XmlPullParser.START_TAG, ns, NODE_DATA); + //Log_OC.d(TAG, "---- NODE DATA ---"); + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) { + continue; + } + String name = parser.getName(); + if (NODE_LIMIT.equalsIgnoreCase(name)) { + downloadLimitResponse.setLimit(Long.parseLong(readNode(parser, NODE_LIMIT))); + } else if (NODE_COUNT.equalsIgnoreCase(name)) { + downloadLimitResponse.setCount(Long.parseLong(readNode(parser, NODE_COUNT))); + } else { + skip(parser); + } + } + + return downloadLimitResponse; + } + + + /** + * Parse a node, to obtain its text. Needs readText method + * + * @param parser + * @param node + * @return Text of the node + * @throws XmlPullParserException + * @throws IOException + */ + private String readNode(XmlPullParser parser, String node) throws XmlPullParserException, + IOException { + parser.require(XmlPullParser.START_TAG, ns, node); + String value = readText(parser); + //Log_OC.d(TAG, "node= " + node + ", value= " + value); + parser.require(XmlPullParser.END_TAG, ns, node); + return value; + } + + + /** + * Read the text from a node + * + * @param parser + * @return Text of the node + * @throws IOException + * @throws XmlPullParserException + */ + private String readText(XmlPullParser parser) throws IOException, XmlPullParserException { + String result = ""; + if (parser.next() == XmlPullParser.TEXT) { + result = parser.getText(); + parser.nextTag(); + } + return result; + } + + /** + * Skip tags in parser procedure + * + * @param parser + * @throws XmlPullParserException + * @throws IOException + */ + private void skip(XmlPullParser parser) throws XmlPullParserException, IOException { + if (parser.getEventType() != XmlPullParser.START_TAG) { + throw new IllegalStateException(); + } + int depth = 1; + while (depth != 0) { + switch (parser.next()) { + case XmlPullParser.END_TAG: + depth--; + break; + case XmlPullParser.START_TAG: + depth++; + break; + } + } + } +} diff --git a/app/src/main/java/com/owncloud/android/operations/share_download_limit/GetShareDownloadLimitOperation.java b/app/src/main/java/com/owncloud/android/operations/share_download_limit/GetShareDownloadLimitOperation.java new file mode 100644 index 000000000000..f10036961823 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/operations/share_download_limit/GetShareDownloadLimitOperation.java @@ -0,0 +1,90 @@ +/** + * ownCloud Android client application + * + * @author TSI-mc 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, as published by the Free Software Foundation. + *

+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + *

+ * You should have received a copy of the GNU General Public License along with this program. If not, see + * . + */ + +package com.owncloud.android.operations.share_download_limit; + +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.common.utils.Log_OC; + +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.methods.GetMethod; + +/** + * class to fetch the download limit for the link share it requires share token to fetch the data + *

+ * API : //GET to /ocs/v2.php/apps/files_downloadlimit/{share_token}/limit + */ +public class GetShareDownloadLimitOperation extends RemoteOperation { + + private static final String TAG = GetShareDownloadLimitOperation.class.getSimpleName(); + + //share token from OCShare + private final String shareToken; + + public GetShareDownloadLimitOperation(String shareToken) { + this.shareToken = shareToken; + } + + @Override + protected RemoteOperationResult run(OwnCloudClient client) { + RemoteOperationResult result = null; + int status = -1; + + GetMethod get = null; + + try { + // Get Method + get = new GetMethod(client.getBaseUri() + ShareDownloadLimitUtils.INSTANCE.getDownloadLimitApiPath(shareToken)); + + get.addRequestHeader(OCS_API_HEADER, OCS_API_HEADER_VALUE); + + status = client.executeMethod(get); + + if (isSuccess(status)) { + String response = get.getResponseBodyAsString(); + + Log_OC.d(TAG, "Get Download Limit response: " + response); + + DownloadLimitXMLParser parser = new DownloadLimitXMLParser(); + result = parser.parse(true, response); + + if (result.isSuccess()) { + Log_OC.d(TAG, "Got " + result.getResultData() + " Response"); + } + + } else { + result = new RemoteOperationResult(false, get); + } + + } catch (Exception e) { + result = new RemoteOperationResult(e); + Log_OC.e(TAG, "Exception while getting share download limit", e); + + } finally { + if (get != null) { + get.releaseConnection(); + } + } + return result; + } + + private boolean isSuccess(int status) { + return (status == HttpStatus.SC_OK); + } + +} diff --git a/app/src/main/java/com/owncloud/android/operations/share_download_limit/ShareDownloadLimitUtils.kt b/app/src/main/java/com/owncloud/android/operations/share_download_limit/ShareDownloadLimitUtils.kt new file mode 100644 index 000000000000..4c49e3bec924 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/operations/share_download_limit/ShareDownloadLimitUtils.kt @@ -0,0 +1,30 @@ +/** + * ownCloud Android client application + * + * @author TSI-mc 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, as published by the Free Software Foundation. + *

+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + *

+ * You should have received a copy of the GNU General Public License along with this program. If not, see + * . + */ + +package com.owncloud.android.operations.share_download_limit + +object ShareDownloadLimitUtils { + + private const val SHARE_TOKEN_PATH = "{share_token}" + + //ocs route + //replace the {share_token} + private const val SHARE_DOWNLOAD_LIMIT_API_PATH = "/ocs/v2.php/apps/files_downloadlimit/api/v1/$SHARE_TOKEN_PATH/limit" + + fun getDownloadLimitApiPath(shareToken: String) : String{ + return SHARE_DOWNLOAD_LIMIT_API_PATH.replace(SHARE_TOKEN_PATH, shareToken) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/owncloud/android/operations/share_download_limit/UpdateShareDownloadLimitRemoteOperation.java b/app/src/main/java/com/owncloud/android/operations/share_download_limit/UpdateShareDownloadLimitRemoteOperation.java new file mode 100644 index 000000000000..f177a515e612 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/operations/share_download_limit/UpdateShareDownloadLimitRemoteOperation.java @@ -0,0 +1,114 @@ +/** + * ownCloud Android client application + * + * @author TSI-mc 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, as published by the Free Software Foundation. + *

+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + *

+ * You should have received a copy of the GNU General Public License along with this program. If not, see + * . + */ + +package com.owncloud.android.operations.share_download_limit; + +import android.util.Pair; + +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.common.utils.Log_OC; + +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.methods.PutMethod; +import org.apache.commons.httpclient.methods.StringRequestEntity; + +import java.util.ArrayList; +import java.util.List; + +/** + * class to update the download limit for the link share + *

+ * API : //PUT to /ocs/v2.php/apps/files_downloadlimit/{share_token}/limit + *

+ * Body: {"token" : "Bpd4oEAgPqn3AbG", "limit" : 5} + */ +public class UpdateShareDownloadLimitRemoteOperation extends RemoteOperation { + + private static final String TAG = UpdateShareDownloadLimitRemoteOperation.class.getSimpleName(); + + private static final String PARAM_TOKEN = "token"; + private static final String PARAM_LIMIT = "limit"; + + private static final String ENTITY_CONTENT_TYPE = "application/x-www-form-urlencoded"; + private static final String ENTITY_CHARSET = "UTF-8"; + + private final String shareToken; + private final long downloadLimit; + + public UpdateShareDownloadLimitRemoteOperation(String shareToken, long downloadLimit) { + this.shareToken = shareToken; + this.downloadLimit = downloadLimit; + } + + @Override + protected RemoteOperationResult run(OwnCloudClient client) { + RemoteOperationResult result; + int status; + + PutMethod put = null; + + try { + // Post Method + put = new PutMethod(client.getBaseUri() + ShareDownloadLimitUtils.INSTANCE.getDownloadLimitApiPath(shareToken)); + + put.addRequestHeader(OCS_API_HEADER, OCS_API_HEADER_VALUE); + List> parametersToUpdate = new ArrayList<>(); + parametersToUpdate.add(new Pair<>(PARAM_TOKEN, shareToken)); + parametersToUpdate.add(new Pair<>(PARAM_LIMIT, String.valueOf(downloadLimit))); + + for (Pair parameter : parametersToUpdate) { + put.setRequestEntity(new StringRequestEntity(parameter.first + "=" + parameter.second, + ENTITY_CONTENT_TYPE, + ENTITY_CHARSET)); + } + + status = client.executeMethod(put); + + if (isSuccess(status)) { + String response = put.getResponseBodyAsString(); + + Log_OC.d(TAG, "Download Limit response: " + response); + + DownloadLimitXMLParser parser = new DownloadLimitXMLParser(); + result = parser.parse(true, response); + + if (result.isSuccess()) { + return result; + } + + } else { + result = new RemoteOperationResult<>(false, put); + } + + } catch (Exception e) { + result = new RemoteOperationResult<>(e); + Log_OC.e(TAG, "Exception while updating share download limit", e); + + } finally { + if (put != null) { + put.releaseConnection(); + } + } + return result; + } + + private boolean isSuccess(int status) { + return status == HttpStatus.SC_OK || status == HttpStatus.SC_BAD_REQUEST; + } + +} diff --git a/app/src/main/java/com/owncloud/android/providers/UsersAndGroupsSearchProvider.java b/app/src/main/java/com/owncloud/android/providers/UsersAndGroupsSearchProvider.java index ef8df0f9c355..c7e90845bc30 100644 --- a/app/src/main/java/com/owncloud/android/providers/UsersAndGroupsSearchProvider.java +++ b/app/src/main/java/com/owncloud/android/providers/UsersAndGroupsSearchProvider.java @@ -81,7 +81,7 @@ public class UsersAndGroupsSearchProvider extends ContentProvider { BaseColumns._ID, SearchManager.SUGGEST_COLUMN_TEXT_1, SearchManager.SUGGEST_COLUMN_TEXT_2, - SearchManager.SUGGEST_COLUMN_ICON_1, + // SearchManager.SUGGEST_COLUMN_ICON_1, SearchManager.SUGGEST_COLUMN_INTENT_DATA }; @@ -323,7 +323,11 @@ private Cursor searchForUsersOrGroups(Uri uri) { displayName = userName; subline = (status.getMessage() == null || status.getMessage().isEmpty()) ? null : status.getMessage(); - Uri.Builder builder = Uri.parse("content://" + AUTHORITY + "/icon").buildUpon(); + icon = R.drawable.ic_internal_share; + + // Commented for NMC customization + // uncomment the below code to show icon with initials + /*Uri.Builder builder = Uri.parse("content://" + AUTHORITY + "/icon").buildUpon(); builder.appendQueryParameter("shareWith", shareWith); builder.appendQueryParameter("displayName", displayName); @@ -333,7 +337,7 @@ private Cursor searchForUsersOrGroups(Uri uri) { builder.appendQueryParameter("icon", status.getIcon()); } - icon = builder.build(); + icon = builder.build();*/ dataUri = Uri.withAppendedPath(userBaseUri, shareWith); break; @@ -362,12 +366,22 @@ private Cursor searchForUsersOrGroups(Uri uri) { } if (displayName != null && dataUri != null) { - response.newRow() - .add(count++) // BaseColumns._ID - .add(displayName) // SearchManager.SUGGEST_COLUMN_TEXT_1 - .add(subline) // SearchManager.SUGGEST_COLUMN_TEXT_2 - .add(icon) // SearchManager.SUGGEST_COLUMN_ICON_1 - .add(dataUri); + //if display name is empty set sublime as primary text + if (displayName.equals("")) { + response.newRow() + .add(count++) // BaseColumns._ID + .add(subline) // SearchManager.SUGGEST_COLUMN_TEXT_1 + .add(displayName) // SearchManager.SUGGEST_COLUMN_TEXT_2 + //.add(icon) // SearchManager.SUGGEST_COLUMN_ICON_1 + .add(dataUri); + } else { + response.newRow() + .add(count++) // BaseColumns._ID + .add(displayName) // SearchManager.SUGGEST_COLUMN_TEXT_1 + .add(subline) // SearchManager.SUGGEST_COLUMN_TEXT_2 + //.add(icon) // SearchManager.SUGGEST_COLUMN_ICON_1 + .add(dataUri); + } } } @@ -449,7 +463,8 @@ public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) thr } catch (FileNotFoundException e) { Log_OC.e(TAG, "File not found: " + e.getMessage()); } - + } catch (OutOfMemoryError oome) { + Log_OC.e(TAG, "Out of memory"); } catch (Exception e) { Log_OC.e(TAG, "Error opening file: " + e.getMessage()); } diff --git a/app/src/main/java/com/owncloud/android/services/OperationsService.java b/app/src/main/java/com/owncloud/android/services/OperationsService.java index 6923606819af..29ed12c27c6b 100644 --- a/app/src/main/java/com/owncloud/android/services/OperationsService.java +++ b/app/src/main/java/com/owncloud/android/services/OperationsService.java @@ -64,6 +64,7 @@ import com.owncloud.android.operations.UpdateShareInfoOperation; import com.owncloud.android.operations.UpdateSharePermissionsOperation; import com.owncloud.android.operations.UpdateShareViaLinkOperation; +import com.owncloud.android.operations.share_download_limit.GetShareDownloadLimitOperation; import java.io.IOException; import java.util.Optional; @@ -101,6 +102,8 @@ public class OperationsService extends Service { public static final String EXTRA_SHARE_ID = "SHARE_ID"; public static final String EXTRA_SHARE_REMOTE_ID = "SHARE_REMOTE_ID"; public static final String EXTRA_SHARE_NOTE = "SHARE_NOTE"; + public static final String EXTRA_SHARE_TOKEN = "SHARE_TOKEN"; + public static final String EXTRA_SHARE_DOWNLOAD_LIMIT = "SHARE_DOWNLOAD_LIMIT"; public static final String EXTRA_IN_BACKGROUND = "IN_BACKGROUND"; public static final String EXTRA_FILES_DOWNLOAD_LIMIT = "FILES_DOWNLOAD_LIMIT"; public static final String EXTRA_SHARE_ATTRIBUTES = "SHARE_ATTRIBUTES"; @@ -123,6 +126,7 @@ public class OperationsService extends Service { public static final String ACTION_MOVE_FILE = "MOVE_FILE"; public static final String ACTION_COPY_FILE = "COPY_FILE"; public static final String ACTION_CHECK_CURRENT_CREDENTIALS = "CHECK_CURRENT_CREDENTIALS"; + public static final String ACTION_GET_SHARE_DOWNLOAD_LIMIT = "GET_SHARE_DOWNLOAD_LIMIT"; public static final String ACTION_RESTORE_VERSION = "RESTORE_VERSION"; public static final String ACTION_UPDATE_FILES_DOWNLOAD_LIMIT = "UPDATE_FILES_DOWNLOAD_LIMIT"; @@ -657,6 +661,12 @@ private Pair newOperation(Intent operationIntent) { String shareAttributes = operationIntent.getStringExtra(EXTRA_SHARE_ATTRIBUTES); updateShare.setAttributes(shareAttributes); + // download limit for link share type + if (operationIntent.hasExtra(EXTRA_SHARE_DOWNLOAD_LIMIT)) { + updateShare.setDownloadLimit(operationIntent.getLongExtra(EXTRA_SHARE_DOWNLOAD_LIMIT, + 0L)); + } + operation = updateShare; } break; @@ -765,6 +775,13 @@ private Pair newOperation(Intent operationIntent) { fileVersion.getFileName()); break; + case ACTION_GET_SHARE_DOWNLOAD_LIMIT: + String shareToken = operationIntent.getStringExtra(EXTRA_SHARE_TOKEN); + if (!TextUtils.isEmpty(shareToken)) { + operation = new GetShareDownloadLimitOperation(shareToken); + } + break; + case ACTION_UPDATE_FILES_DOWNLOAD_LIMIT: shareId = operationIntent.getLongExtra(EXTRA_SHARE_ID, -1); int newLimit = operationIntent.getIntExtra(EXTRA_FILES_DOWNLOAD_LIMIT, -1); diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java index 944b15a0c83c..b94b4aad1b40 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java @@ -76,6 +76,8 @@ import com.owncloud.android.operations.UpdateSharePermissionsOperation; import com.owncloud.android.operations.UpdateShareViaLinkOperation; import com.owncloud.android.providers.UsersAndGroupsSearchConfig; +import com.owncloud.android.operations.share_download_limit.DownloadLimitResponse; +import com.owncloud.android.operations.share_download_limit.GetShareDownloadLimitOperation; import com.owncloud.android.providers.UsersAndGroupsSearchProvider; import com.owncloud.android.services.OperationsService; import com.owncloud.android.services.OperationsService.OperationsServiceBinder; @@ -90,6 +92,7 @@ import com.owncloud.android.ui.events.FavoriteEvent; import com.owncloud.android.ui.fragment.FileDetailFragment; import com.owncloud.android.ui.fragment.FileDetailSharingFragment; +import com.owncloud.android.ui.fragment.FileDetailsSharingProcessFragment; import com.owncloud.android.ui.fragment.OCFileListFragment; import com.owncloud.android.ui.fragment.filesRepository.FilesRepository; import com.owncloud.android.ui.fragment.filesRepository.RemoteFilesRepository; @@ -410,7 +413,8 @@ public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationRe operation instanceof UnshareOperation || operation instanceof SynchronizeFolderOperation || operation instanceof UpdateShareViaLinkOperation || - operation instanceof UpdateSharePermissionsOperation + operation instanceof UpdateSharePermissionsOperation || + operation instanceof UpdateShareInfoOperation ) { if (result.isSuccess()) { updateFileFromDB(); @@ -450,6 +454,8 @@ public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationRe onUpdateShareInformation(result, R.string.unsharing_failed); } else if (operation instanceof UpdateNoteForShareOperation) { onUpdateNoteForShareOperationFinish(result); + } else if (operation instanceof GetShareDownloadLimitOperation) { + onShareDownloadLimitFetched(result); } } @@ -829,7 +835,6 @@ public void refreshList() { private void onCreateShareViaLinkOperationFinish(CreateShareViaLinkOperation operation, RemoteOperationResult result) { FileDetailSharingFragment sharingFragment = getShareFileFragment(); - final Fragment fileListFragment = getSupportFragmentManager().findFragmentByTag(FileDisplayActivity.TAG_LIST_OF_FILES); if (result.isSuccess()) { updateFileFromDB(); @@ -856,6 +861,8 @@ private void onCreateShareViaLinkOperationFinish(CreateShareViaLinkOperation ope sharingFragment.onUpdateShareInformation(result, file); } + // this has to be here to avoid the crash when creating link from inside of FileDetailSharingFragment + Fragment fileListFragment = getSupportFragmentManager().findFragmentByTag(FileDisplayActivity.TAG_LIST_OF_FILES); if (fileListFragment instanceof OCFileListFragment ocFileListFragment && file != null) { if (ocFileListFragment.getAdapterFiles().contains(file)) { ocFileListFragment.updateOCFile(file); @@ -895,6 +902,22 @@ private void onCreateShareViaLinkOperationFinish(CreateShareViaLinkOperation ope } } + /** + * method will be called when download limit is fetched + * + * @param result + */ + private void onShareDownloadLimitFetched(RemoteOperationResult result) { + FileDetailSharingFragment sharingFragment = getShareFileFragment(); + + if (result.isSuccess() && sharingFragment != null && result.getResultData() != null + && result.getResultData() instanceof DownloadLimitResponse) { + onLinkShareDownloadLimitFetched(((DownloadLimitResponse) result.getResultData()).getLimit(), + ((DownloadLimitResponse) result.getResultData()).getCount()); + + } + } + /** * Shortcut to get access to the {@link FileDetailSharingFragment} instance, if any * @@ -962,11 +985,17 @@ private OCFile getFileFromDetailFragment() { * @param shareType */ protected void doShareWith(String shareeName, ShareType shareType) { - FileDetailFragment fragment = getFileDetailFragment(); + Fragment fragment = getFileDetailFragment(); if (fragment != null) { - fragment.initiateSharingProcess(shareeName, - shareType, - usersAndGroupsSearchConfig.getSearchOnlyUsers()); + ((FileDetailFragment) fragment).initiateSharingProcess(shareeName, + shareType, + usersAndGroupsSearchConfig.getSearchOnlyUsers()); + } else { + //if user sharing from Preview Image Fragment + fragment = getSupportFragmentManager().findFragmentByTag(ShareActivity.TAG_SHARE_FRAGMENT); + if (fragment != null) { + ((FileDetailSharingFragment) fragment).initiateSharingProcess(shareeName, shareType, usersAndGroupsSearchConfig.getSearchOnlyUsers()); + } } } @@ -978,9 +1007,23 @@ protected void doShareWith(String shareeName, ShareType shareType) { */ @Override public void editExistingShare(OCShare share, int screenTypePermission, boolean isReshareShown) { - FileDetailFragment fragment = getFileDetailFragment(); + Fragment fragment = getFileDetailFragment(); if (fragment != null) { - fragment.editExistingShare(share, screenTypePermission, isReshareShown); + ((FileDetailFragment) fragment).editExistingShare(share, screenTypePermission, isReshareShown); + } else { + //if user editing from Preview Image Fragment + fragment = getSupportFragmentManager().findFragmentByTag(ShareActivity.TAG_SHARE_FRAGMENT); + if (fragment != null) { + ((FileDetailSharingFragment) fragment).editExistingShare(share, screenTypePermission, isReshareShown); + } + } + } + + @Override + public void onLinkShareDownloadLimitFetched(long downloadLimit, long downloadCount) { + Fragment fileDetailsSharingProcessFragment = getSupportFragmentManager().findFragmentByTag(FileDetailsSharingProcessFragment.TAG); + if (fileDetailsSharingProcessFragment != null) { + ((FileDetailsSharingProcessFragment) fileDetailsSharingProcessFragment).onLinkShareDownloadLimitFetched(downloadLimit, downloadCount); } } @@ -991,7 +1034,7 @@ public void editExistingShare(OCShare share, int screenTypePermission, boolean i public void onShareProcessClosed() { FileDetailFragment fragment = getFileDetailFragment(); if (fragment != null) { - fragment.showHideFragmentView(false); + //do something } } diff --git a/app/src/main/java/com/owncloud/android/ui/activity/ShareActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/ShareActivity.java index 3c6b724e553f..5f04b79c7006 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/ShareActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/ShareActivity.java @@ -32,6 +32,7 @@ import com.owncloud.android.operations.GetSharesForFileOperation; import com.owncloud.android.ui.fragment.FileDetailSharingFragment; import com.owncloud.android.ui.fragment.FileDetailsSharingProcessFragment; +import com.owncloud.android.ui.fragment.util.SharePermissionManager; import com.owncloud.android.utils.DisplayUtils; import com.owncloud.android.utils.MimeTypeUtil; @@ -132,11 +133,12 @@ protected void onStart() { @Override protected void doShareWith(String shareeName, ShareType shareType) { - getSupportFragmentManager().beginTransaction().replace(R.id.share_fragment_container, + getSupportFragmentManager().beginTransaction().add(R.id.share_fragment_container, FileDetailsSharingProcessFragment.newInstance(getFile(), shareeName, shareType, - false), + false, + SharePermissionManager.canEditFile(getUser().get(), getStorageManager().getCapability(getUser().get()), getFile(), editorUtils)), FileDetailsSharingProcessFragment.TAG) .commit(); } diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/LinkShareViewHolder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/LinkShareViewHolder.kt index 4c6f3aa59af1..5b34170a5e66 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/LinkShareViewHolder.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/LinkShareViewHolder.kt @@ -19,12 +19,13 @@ import android.view.View import androidx.core.content.res.ResourcesCompat import androidx.recyclerview.widget.RecyclerView import com.nextcloud.utils.mdm.MDMConfig +import com.nmc.android.utils.EllipsizeListener +import com.nmc.android.utils.TextViewUtils import com.owncloud.android.R import com.owncloud.android.databinding.FileDetailsShareLinkShareItemBinding -import com.owncloud.android.datamodel.quickPermission.QuickPermissionType import com.owncloud.android.lib.resources.shares.OCShare import com.owncloud.android.lib.resources.shares.ShareType -import com.owncloud.android.ui.fragment.util.SharePermissionManager.getSelectedType +import com.owncloud.android.ui.fragment.util.SharePermissionManager import com.owncloud.android.ui.fragment.util.SharePermissionManager.isSecureFileDrop import com.owncloud.android.utils.theme.ViewThemeUtils @@ -47,11 +48,10 @@ internal class LinkShareViewHolder(itemView: View) : RecyclerView.ViewHolder(ite } fun bind(publicShare: OCShare, listener: ShareeListAdapterListener, position: Int) { - val quickPermissionType = getSelectedType(publicShare, encrypted) - setName(binding, context, publicShare, position) - setSubline(binding, context, publicShare) - setPermissionName(binding, context, publicShare, quickPermissionType) + setPermissionName(binding, context, publicShare) + showHideCalendarIcon(publicShare.expirationDate) + showHidePasswordIcon(publicShare.isPasswordProtected) setOnClickListeners(binding, listener, publicShare) configureCopyLink(binding, context, listener, publicShare) } @@ -79,15 +79,22 @@ internal class LinkShareViewHolder(itemView: View) : RecyclerView.ViewHolder(ite else -> context.getString(R.string.share_link_with_label, label) } + + binding.overflowMenu.setVisibility(View.GONE) + binding.copyLink.setVisibility(View.VISIBLE) + binding.detailText.visibility = View.VISIBLE + return } if (ShareType.EMAIL == publicShare.shareType) { binding.name.text = publicShare.sharedWithDisplayName - val emailDrawable = ResourcesCompat.getDrawable(context.resources, R.drawable.ic_email, null) + val emailDrawable = ResourcesCompat.getDrawable(context.resources, R.drawable.ic_external_share, null) binding.icon.setImageDrawable(emailDrawable) + binding.overflowMenu.setVisibility(View.VISIBLE) binding.copyLink.visibility = View.GONE + binding.detailText.visibility = View.GONE return } @@ -99,48 +106,65 @@ internal class LinkShareViewHolder(itemView: View) : RecyclerView.ViewHolder(ite binding.name.text = context.getString(R.string.share_link_with_label, label) } - private fun setSubline(binding: FileDetailsShareLinkShareItemBinding?, context: Context?, publicShare: OCShare) { - if (binding == null || context == null) { - return - } - - val downloadLimit = publicShare.fileDownloadLimit - if (downloadLimit != null && downloadLimit.limit > 0) { - val remaining = downloadLimit.limit - downloadLimit.count - val text = context.resources.getQuantityString( - R.plurals.share_download_limit_description, - remaining, - remaining - ) - - binding.subline.text = text - binding.subline.visibility = View.VISIBLE - return - } - - binding.subline.visibility = View.GONE - } - private fun setPermissionName( binding: FileDetailsShareLinkShareItemBinding?, context: Context?, - publicShare: OCShare?, - quickPermissionType: QuickPermissionType + publicShare: OCShare? ) { if (binding == null || context == null) { return } - val permissionName = quickPermissionType.getText(context) + val permissionName = SharePermissionManager.getPermissionName(context, publicShare) if (TextUtils.isEmpty(permissionName) || (isSecureFileDrop(publicShare) && encrypted)) { - binding.permissionName.visibility = View.GONE + binding.quickPermissionLayout.permissionLayout.visibility = View.GONE return } - binding.permissionName.text = permissionName - binding.permissionName.visibility = View.VISIBLE - viewThemeUtils?.androidx?.colorPrimaryTextViewElement(binding.permissionName) + binding.quickPermissionLayout.permissionName.text = permissionName + + TextViewUtils.isTextEllipsized(binding.quickPermissionLayout.permissionName, object : EllipsizeListener { + override fun onResult(isEllipsized: Boolean) { + if (isEllipsized) { + binding.quickPermissionLayout.permissionName.text = + SharePermissionManager.getShortPermissionName(context, permissionName) + } + } + }) + setPermissionTypeIcon(permissionName) + binding.quickPermissionLayout.permissionLayout.visibility = View.VISIBLE + } + + private fun showHideCalendarIcon(expirationDate: Long) { + binding?.quickPermissionLayout?.calendarPermissionIcon?.setVisibility(if (expirationDate > 0) View.VISIBLE else View.GONE) + } + + private fun showHidePasswordIcon(isPasswordProtected: Boolean) { + binding?.quickPermissionLayout?.passwordPermissionIcon?.setVisibility(if (isPasswordProtected) View.VISIBLE else View.GONE) + } + + private fun setPermissionTypeIcon(permissionName: String?) { + when (permissionName) { + context?.resources?.getString(R.string.share_quick_permission_can_edit) -> { + binding?.quickPermissionLayout?.permissionTypeIcon?.setImageResource(R.drawable.ic_sharing_edit) + binding?.quickPermissionLayout?.permissionTypeIcon?.setVisibility(View.VISIBLE) + } + + context?.resources?.getString(R.string.share_quick_permission_can_view) -> { + binding?.quickPermissionLayout?.permissionTypeIcon?.setImageResource(R.drawable.ic_sharing_read_only) + binding?.quickPermissionLayout?.permissionTypeIcon?.setVisibility(View.VISIBLE) + } + + context?.resources?.getString(R.string.share_permission_secure_file_drop), context?.resources?.getString(R.string.share_quick_permission_can_upload) -> { + binding?.quickPermissionLayout?.permissionTypeIcon?.setImageResource(R.drawable.ic_sharing_file_drop) + binding?.quickPermissionLayout?.permissionTypeIcon?.setVisibility(View.VISIBLE) + } + + else -> { + binding?.quickPermissionLayout?.permissionTypeIcon?.setVisibility(View.GONE) + } + } } private fun setOnClickListeners( @@ -152,14 +176,15 @@ internal class LinkShareViewHolder(itemView: View) : RecyclerView.ViewHolder(ite return } - viewThemeUtils?.platform?.colorImageViewBackgroundAndIcon(binding.icon) - binding.overflowMenu.setOnClickListener { listener.showSharingMenuActionSheet(publicShare) } binding.shareByLinkContainer.setOnClickListener { listener.showPermissionsDialog(publicShare) } + binding.detailText.setOnClickListener { + listener.showSharingMenuActionSheet(publicShare) + } } private fun configureCopyLink( diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/ListViewHolder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/ListViewHolder.kt index 44e9fa7e8ee8..d534cb665727 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/ListViewHolder.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/ListViewHolder.kt @@ -22,6 +22,7 @@ interface ListViewHolder { val localFileIndicator: ImageView val imageFileName: TextView? val shared: ImageView + val sharedMessage: TextView? val checkbox: ImageView val itemLayout: View val unreadComments: ImageView diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java index 83f6a8e3538e..f604a36c2e0a 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java +++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java @@ -618,7 +618,8 @@ private void handleListMode(ListGridItemViewHolder holder, private void bindListItemViewHolder(ListItemViewHolder holder, OCFile file) { if ((file.isSharedWithMe() || file.isSharedWithSharee()) && !isMultiSelect() && !gridView && !hideItemOptions) { - holder.getSharedAvatars().setVisibility(View.VISIBLE); + //visibility gone as view not required for NMC + holder.getSharedAvatars().setVisibility(View.GONE); holder.getSharedAvatars().removeAllViews(); String fileOwner = file.getOwnerId(); diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt index 1a11fe7a3251..00fd54bf8ee9 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt @@ -13,6 +13,8 @@ import android.os.AsyncTask import android.view.View import android.widget.ImageView import androidx.core.content.ContextCompat +import android.widget.TextView +import androidx.appcompat.content.res.AppCompatResources import androidx.core.content.res.ResourcesCompat import androidx.core.graphics.drawable.toDrawable import com.elyeproj.loaderviewlibrary.LoaderImageView @@ -271,8 +273,9 @@ class OCFileListDelegate( ) // sharing an encrypted subfolder is not possible if (shouldHideShare) { gridViewHolder.shared.visibility = View.GONE + gridViewHolder.sharedMessage?.visibility = View.GONE } else { - configureSharedIconView(gridViewHolder, file) + showShareIcon(gridViewHolder, file) } if (!file.isOfflineOperation && !file.isFolder) { @@ -420,39 +423,77 @@ class OCFileListDelegate( } } - private fun configureSharedIconView(gridViewHolder: ListViewHolder, file: OCFile) { - val result = getShareIconIdAndContentDescriptionId(gridViewHolder, file) - - gridViewHolder.shared.run { - if (result == null) { - visibility = View.GONE - return - } - - setImageResource(result.first) - contentDescription = context.getString(result.second) - visibility = View.VISIBLE - setOnClickListener { ocFileListFragmentInterface.onShareIconClick(file) } - } - } - - @Suppress("ReturnCount") - private fun getShareIconIdAndContentDescriptionId(holder: ListViewHolder, file: OCFile): Pair? { + private fun showShareIcon(gridViewHolder: ListViewHolder, file: OCFile) { + val sharedIconView = gridViewHolder.shared + //Initialising Textview for Message and setting its visibility + //only applicable for list item + val sharedMessageView: TextView? = gridViewHolder.sharedMessage if (!MDMConfig.sharingSupport(context)) { - return null + sharedIconView.visibility = View.GONE + sharedMessageView?.visibility = View.GONE + return } - - if (file.isOfflineOperation) return null - - if (holder !is OCFileListItemViewHolder && file.unreadCommentsCount != 0) return null - - return when { - file.isSharedWithSharee || file.isSharedWithMe -> { - if (showShareAvatar) null else R.drawable.shared_via_users to R.string.shared_icon_shared + sharedMessageView?.visibility = if (com.nmc.android.utils.DisplayUtils.isShowDividerForList()) View.VISIBLE else View.GONE + + if (gridViewHolder is OCFileListItemViewHolder || file.unreadCommentsCount == 0) { + sharedIconView.visibility = View.VISIBLE + when { + file.isSharedWithMe -> { + val sharedWithMeColor = ResourcesCompat.getColor( + context.resources, + R.color.shared_with_me_color, null + ) + val shareWithMeIcon = AppCompatResources.getDrawable(context, R.drawable.ic_shared_with_me)?.mutate() + val shareWithMeTintedIcon = + viewThemeUtils.platform.colorDrawable(shareWithMeIcon!!, sharedWithMeColor) + sharedIconView.setImageDrawable(shareWithMeTintedIcon) + sharedIconView.contentDescription = context.getString(R.string.shared_icon_shared) + //Added Code For Message Text + sharedMessageView?.text = context.resources.getString(R.string.placeholder_receivedMessage) + sharedMessageView?.setTextColor(sharedWithMeColor) + } + file.isSharedWithSharee -> { + val shareIcon = viewThemeUtils.platform.colorDrawable( + AppCompatResources.getDrawable(context, R.drawable.ic_share)?.mutate()!!, + context.resources.getColor(R.color.primary, null) + ) + sharedIconView.setImageDrawable(shareIcon) + sharedIconView.contentDescription = context.getString(R.string.shared_icon_shared) + //Added Code For Message Text + sharedMessageView?.text = context.resources.getString(R.string.placeholder_sharedMessage) + sharedMessageView?.setTextColor(context.resources.getColor(R.color.primary, null)) + } + file.isSharedViaLink -> { + val shareIcon = viewThemeUtils.platform.colorDrawable( + AppCompatResources.getDrawable(context, R.drawable.ic_share)?.mutate()!!, + context.resources.getColor(R.color.primary, null) + ) + sharedIconView.setImageDrawable(shareIcon) + sharedIconView.contentDescription = context.getString(R.string.shared_icon_shared_via_link) + //Added Code For Message Text + sharedMessageView?.text = context.resources.getString(R.string.placeholder_sharedMessage) + sharedMessageView?.setTextColor(context.resources.getColor(R.color.primary, null)) + } + file.isEncrypted -> { + sharedIconView.visibility = View.GONE + sharedMessageView?.visibility = View.GONE + } + else -> { + val unShareIconColor = ResourcesCompat.getColor( + context.resources, + R.color.list_icon_color, null + ) + val unShareIcon = AppCompatResources.getDrawable(context, R.drawable.ic_share)?.mutate() + val unShareTintedIcon = viewThemeUtils.platform.colorDrawable(unShareIcon!!, unShareIconColor) + sharedIconView.setImageDrawable(unShareTintedIcon) + sharedIconView.contentDescription = context.getString(R.string.shared_icon_share) + sharedMessageView?.visibility = View.GONE + } } - - file.isSharedViaLink -> R.drawable.shared_via_link to R.string.shared_icon_shared_via_link - else -> R.drawable.ic_unshared to R.string.shared_icon_share + sharedIconView.setOnClickListener { ocFileListFragmentInterface.onShareIconClick(file) } + } else { + sharedIconView.visibility = View.GONE + sharedMessageView?.visibility = View.GONE } } diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListGridItemViewHolder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListGridItemViewHolder.kt index dc00b032e3c7..acd0cb67a94f 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListGridItemViewHolder.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListGridItemViewHolder.kt @@ -49,6 +49,8 @@ class OCFileListGridItemViewHolder(var binding: GridItemBinding) : get() = null override val shared: ImageView get() = binding.sharedIcon + override val sharedMessage: TextView? + get() = null override val checkbox: ImageView get() = binding.customCheckbox override val itemLayout: View diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListItemViewHolder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListItemViewHolder.kt index 96da2b7e5c67..fa3ead9c6bbc 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListItemViewHolder.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListItemViewHolder.kt @@ -76,6 +76,8 @@ internal class OCFileListItemViewHolder(private var binding: ListItemBinding) : get() = null override val shared: ImageView get() = binding.sharedIcon + override val sharedMessage: TextView + get() = binding.sharedMessage override val checkbox: ImageView get() = binding.customCheckbox override val itemLayout: View diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListRecommendedItemViewHolder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListRecommendedItemViewHolder.kt index b5f4b3368d52..f964b7bd16c0 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListRecommendedItemViewHolder.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListRecommendedItemViewHolder.kt @@ -28,6 +28,7 @@ class OCFileListRecommendedItemViewHolder(private val binding: RecommendedFileIt override val favorite: ImageView get() = binding.favoriteAction override val localFileIndicator: ImageView get() = binding.localFileIndicator override val shared: ImageView get() = binding.sharedIcon + override val sharedMessage: TextView? get() = null override val checkbox: ImageView get() = binding.customCheckbox override val itemLayout: View get() = binding.recommendedFileItemLayout override val unreadComments: ImageView get() = binding.unreadComments diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListViewHolder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListViewHolder.kt index b0a0c037cd3a..60579b91ba63 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListViewHolder.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListViewHolder.kt @@ -45,6 +45,8 @@ internal class OCFileListViewHolder(var binding: GridItemBinding) : get() = binding.localFileIndicator override val shared: ImageView get() = binding.sharedIcon + override val sharedMessage: TextView? + get() = null override val checkbox: ImageView get() = binding.customCheckbox override val itemLayout: View diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/QuickSharingPermissionsAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/QuickSharingPermissionsAdapter.kt index 4ac0e862160b..77dfadad8004 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/QuickSharingPermissionsAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/QuickSharingPermissionsAdapter.kt @@ -14,7 +14,6 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView -import com.google.android.material.button.MaterialButton import com.owncloud.android.R import com.owncloud.android.databinding.ItemQuickSharePermissionsBinding import com.owncloud.android.datamodel.quickPermission.QuickPermission @@ -50,12 +49,12 @@ class QuickSharingPermissionsAdapter( val permissionName = quickPermission.type.getText(context) binding.run { - quickPermissionButton.text = permissionName - quickPermissionButton.iconGravity = MaterialButton.ICON_GRAVITY_START - quickPermissionButton.icon = quickPermission.type.getIcon(context) + tvQuickShareName.text = permissionName if (quickPermission.isSelected) { - viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(quickPermissionButton) + tvQuickShareCheckIcon.visibility = View.VISIBLE + } else { + tvQuickShareCheckIcon.visibility = View.INVISIBLE } } diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/ShareViewHolder.java b/app/src/main/java/com/owncloud/android/ui/adapter/ShareViewHolder.java index 26159013d544..a79e9140c596 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/ShareViewHolder.java +++ b/app/src/main/java/com/owncloud/android/ui/adapter/ShareViewHolder.java @@ -20,9 +20,9 @@ import com.nextcloud.client.account.User; import com.nextcloud.utils.extensions.ImageViewExtensionsKt; +import com.nmc.android.utils.TextViewUtils; import com.owncloud.android.R; import com.owncloud.android.databinding.FileDetailsShareShareItemBinding; -import com.owncloud.android.datamodel.quickPermission.QuickPermissionType; import com.owncloud.android.lib.resources.shares.OCShare; import com.owncloud.android.ui.TextDrawable; import com.owncloud.android.ui.fragment.util.SharePermissionManager; @@ -75,18 +75,18 @@ public void bind(OCShare share, switch (share.getShareType()) { case GROUP: name = context.getString(R.string.share_group_clarification, name); - viewThemeUtils.files.createAvatar(share.getShareType(), binding.icon, context); + // viewThemeUtils.files.createAvatar(share.getShareType(), binding.icon, context); break; case ROOM: name = context.getString(R.string.share_room_clarification, name); - viewThemeUtils.files.createAvatar(share.getShareType(), binding.icon, context); + // viewThemeUtils.files.createAvatar(share.getShareType(), binding.icon, context); break; case CIRCLE: - viewThemeUtils.files.createAvatar(share.getShareType(), binding.icon, context); + // viewThemeUtils.files.createAvatar(share.getShareType(), binding.icon, context); break; case FEDERATED: name = context.getString(R.string.share_remote_clarification, name); - setImage(binding.icon, share.getSharedWithDisplayName()); + // setImage(binding.icon, share.getSharedWithDisplayName()); break; case FEDERATED_GROUP: name = context.getString(R.string.share_remote_clarification, name); @@ -94,7 +94,7 @@ public void bind(OCShare share, break; case USER: binding.icon.setTag(share.getShareWith()); - float avatarRadius = context.getResources().getDimension(R.dimen.list_item_avatar_icon_radius); + /* float avatarRadius = context.getResources().getDimension(R.dimen.list_item_avatar_icon_radius); if (share.getShareWith() != null) { DisplayUtils.setAvatar(user, @@ -105,11 +105,11 @@ public void bind(OCShare share, context.getResources(), binding.icon, context); - } - - binding.icon.setOnClickListener(v -> listener.showProfileBottomSheet(user, share.getShareWith())); + } */ + // Not required for NMC as per NMC-3097 + // binding.icon.setOnClickListener(v -> listener.showProfileBottomSheet(user, share.getShareWith())); default: - setImage(binding.icon, name); + // setImage(binding.icon, name); break; } } @@ -120,8 +120,9 @@ public void bind(OCShare share, share.getUserId() != null && share.getUserId().equalsIgnoreCase(userId)) { binding.overflowMenu.setVisibility(View.VISIBLE); - QuickPermissionType quickPermissionType = SharePermissionManager.INSTANCE.getSelectedType(share, encrypted); - setPermissionName(quickPermissionType.getText(context)); + setPermissionName(SharePermissionManager.getPermissionName(context, share)); + showHideCalendarIcon(share.getExpirationDate()); + showHidePasswordIcon(share.isPasswordProtected()); // bind listener to edit privileges binding.overflowMenu.setOnClickListener(v -> listener.showSharingMenuActionSheet(share)); @@ -133,13 +134,43 @@ public void bind(OCShare share, private void setPermissionName(String permissionName) { if (!TextUtils.isEmpty(permissionName)) { - binding.permissionName.setText(permissionName); - binding.permissionName.setVisibility(View.VISIBLE); + binding.quickPermissionLayout.permissionName.setText(permissionName); + TextViewUtils.isTextEllipsized(binding.quickPermissionLayout.permissionName, isEllipsized -> { + if(isEllipsized) { + binding.quickPermissionLayout.permissionName.setText(SharePermissionManager.getShortPermissionName(context, permissionName)); + } + }); + setPermissionTypeIcon(permissionName); + binding.quickPermissionLayout.permissionLayout.setVisibility(View.VISIBLE); + } else { + binding.quickPermissionLayout.permissionLayout.setVisibility(View.GONE); + } + } + + private void setPermissionTypeIcon(String permissionName) { + if (permissionName.equalsIgnoreCase(context.getResources().getString(R.string.share_quick_permission_can_edit))) { + binding.quickPermissionLayout.permissionTypeIcon.setImageResource(R.drawable.ic_sharing_edit); + binding.quickPermissionLayout.permissionTypeIcon.setVisibility(View.VISIBLE); + } else if (permissionName.equalsIgnoreCase(context.getResources().getString(R.string.share_quick_permission_can_view))) { + binding.quickPermissionLayout.permissionTypeIcon.setImageResource(R.drawable.ic_sharing_read_only); + binding.quickPermissionLayout.permissionTypeIcon.setVisibility(View.VISIBLE); + } else if (permissionName.equalsIgnoreCase(context.getResources().getString(R.string.share_permission_secure_file_drop)) + || permissionName.equalsIgnoreCase(context.getResources().getString(R.string.share_quick_permission_can_upload))) { + binding.quickPermissionLayout.permissionTypeIcon.setImageResource(R.drawable.ic_sharing_file_drop); + binding.quickPermissionLayout.permissionTypeIcon.setVisibility(View.VISIBLE); } else { - binding.permissionName.setVisibility(View.GONE); + binding.quickPermissionLayout.permissionTypeIcon.setVisibility(View.GONE); } } + private void showHideCalendarIcon(long expirationDate) { + binding.quickPermissionLayout.calendarPermissionIcon.setVisibility(expirationDate > 0 ? View.VISIBLE : View.GONE); + } + + private void showHidePasswordIcon(boolean isPasswordProtected) { + binding.quickPermissionLayout.passwordPermissionIcon.setVisibility(isPasswordProtected ? View.VISIBLE : View.GONE); + } + private void setImage(ImageView avatar, String name) { if (TextUtils.isEmpty(name)) { setUserImage(avatar); diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/ShareeListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/ShareeListAdapter.kt index f171602f625c..d9075658cee8 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/ShareeListAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/ShareeListAdapter.kt @@ -25,7 +25,6 @@ import com.owncloud.android.databinding.FileDetailsShareLinkShareItemBinding import com.owncloud.android.databinding.FileDetailsSharePublicLinkAddNewItemBinding import com.owncloud.android.databinding.FileDetailsShareSecureFileDropAddNewItemBinding import com.owncloud.android.databinding.FileDetailsShareShareItemBinding -import com.owncloud.android.datamodel.SharesType import com.owncloud.android.lib.resources.shares.OCShare import com.owncloud.android.lib.resources.shares.ShareType import com.owncloud.android.ui.activity.FileActivity @@ -44,12 +43,13 @@ class ShareeListAdapter( private val userId: String?, private val user: User?, private val viewThemeUtils: ViewThemeUtils, - private val encrypted: Boolean, - private val sharesType: SharesType? + private val encrypted: Boolean ) : RecyclerView.Adapter(), AvatarGenerationListener { private val avatarRadiusDimension: Float = fileActivity.getResources().getDimension(R.dimen.user_icon_radius) - var isShowAll: Boolean = false + // NMC-4219 fix + // NMC: show all shares + var isShowAll: Boolean = true private set init { @@ -180,10 +180,5 @@ class ShareeListAdapter( } }.thenByDescending { it.sharedDate } ) - - // add internal share link at end - if (!encrypted && sharesType == SharesType.INTERNAL) { - shares.add(OCShare().apply { shareType = ShareType.INTERNAL }) - } } } diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/SendShareDialog.kt b/app/src/main/java/com/owncloud/android/ui/dialog/SendShareDialog.kt index 067dac7e767d..4a11912b629e 100644 --- a/app/src/main/java/com/owncloud/android/ui/dialog/SendShareDialog.kt +++ b/app/src/main/java/com/owncloud/android/ui/dialog/SendShareDialog.kt @@ -23,7 +23,6 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.snackbar.Snackbar -import com.nextcloud.android.common.ui.theme.utils.ColorRole import com.nextcloud.client.di.Injectable import com.nextcloud.client.utils.IntentUtil.createSendIntent import com.nextcloud.utils.extensions.getParcelableArgument @@ -83,7 +82,6 @@ class SendShareDialog : binding.btnLink.visibility = View.GONE } - applyTintColor() setupBottomSheetBehaviour() checkButtonVisibilities() setupSendButtonRecyclerView() @@ -111,12 +109,6 @@ class SendShareDialog : bottomSheetDialog.behavior.skipCollapsed = true } - private fun applyTintColor() { - viewThemeUtils?.material?.colorMaterialButtonPrimaryFilled(binding.btnLink) - viewThemeUtils?.material?.colorMaterialButtonPrimaryFilled(binding.btnShare) - viewThemeUtils?.platform?.colorViewBackground(binding.bottomSheet, ColorRole.SURFACE) - } - @Suppress("MagicNumber") private fun checkButtonVisibilities() { if (hideNcSharingOptions) { @@ -140,6 +132,9 @@ class SendShareDialog : } private fun shareByLink() { + // NMC Customization + isPeopleShareClicked = false + val fileOperationsHelper = (requireActivity() as FileActivity).fileOperationsHelper if (file?.isSharedViaLink == true) { @@ -223,6 +218,9 @@ class SendShareDialog : } private fun shareFile(file: OCFile?) { + // NMC Customization + isPeopleShareClicked = true + dismiss() if (activity is FileDisplayActivity) { @@ -249,6 +247,11 @@ class SendShareDialog : const val PACKAGE_NAME = "PACKAGE_NAME" const val ACTIVITY_NAME = "ACTIVITY_NAME" + // TODO: 06/21/23 remove this condition after Comments section included + // flag to avoid crash during creating new link share for a file for which link share already exist + @JvmField + var isPeopleShareClicked = false + @JvmStatic fun newInstance(file: OCFile?, hideNcSharingOptions: Boolean, capability: OCCapability): SendShareDialog { val dialogFragment = SendShareDialog() diff --git a/app/src/main/java/com/owncloud/android/ui/events/ShareSearchViewFocusEvent.kt b/app/src/main/java/com/owncloud/android/ui/events/ShareSearchViewFocusEvent.kt new file mode 100644 index 000000000000..0c695ef103b1 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/events/ShareSearchViewFocusEvent.kt @@ -0,0 +1,28 @@ +/* + * Nextcloud Android client application + * + * @author TSI-mc + * Copyright (C) 2022 TSI-mc + * Copyright (C) 2022 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.owncloud.android.ui.events + +/** + * Event for search view focus while sharing a file/folder + * this event will be used to hide the view only for landscape mode so that user will have more space + */ +class ShareSearchViewFocusEvent(val hasFocus: Boolean) \ No newline at end of file diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java index 858c02ec128a..f69ab182e18e 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java @@ -13,6 +13,7 @@ import android.content.res.ColorStateList; import android.graphics.Bitmap; import android.graphics.Color; +import android.graphics.PorterDuff; import android.os.Bundle; import android.view.LayoutInflater; import android.view.Menu; @@ -22,7 +23,6 @@ import com.google.android.material.chip.Chip; 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; import com.nextcloud.client.di.Injectable; @@ -35,16 +35,17 @@ import com.nextcloud.model.WorkerState; import com.nextcloud.model.WorkerStateLiveData; import com.nextcloud.ui.fileactions.FileAction; +import com.nextcloud.utils.EditorUtils; import com.nextcloud.ui.fileactions.FileActionsBottomSheet; import com.nextcloud.utils.MenuUtils; import com.nextcloud.utils.extensions.BundleExtensionsKt; import com.nextcloud.utils.extensions.FileExtensionsKt; -import com.nextcloud.utils.mdm.MDMConfig; import com.owncloud.android.MainApp; import com.owncloud.android.R; import com.owncloud.android.databinding.FileDetailsFragmentBinding; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.datamodel.SyncedFolderProvider; import com.owncloud.android.datamodel.ThumbnailsCacheManager; import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.operations.RemoteOperationResult; @@ -57,15 +58,16 @@ import com.owncloud.android.ui.activity.FileActivity; import com.owncloud.android.ui.activity.FileDisplayActivity; import com.owncloud.android.ui.activity.ToolbarActivity; -import com.owncloud.android.ui.adapter.FileDetailTabAdapter; import com.owncloud.android.ui.adapter.progressListener.DownloadProgressListener; import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment; import com.owncloud.android.ui.dialog.RenameFileDialogFragment; import com.owncloud.android.ui.events.EventBusFactory; +import com.owncloud.android.ui.dialog.SendShareDialog; import com.owncloud.android.ui.events.FavoriteEvent; import com.owncloud.android.ui.events.FileDownloadProgressEvent; +import com.owncloud.android.ui.events.ShareSearchViewFocusEvent; +import com.owncloud.android.ui.fragment.util.SharePermissionManager; import com.owncloud.android.utils.DisplayUtils; -import com.owncloud.android.utils.EncryptionUtils; import com.owncloud.android.utils.MimeTypeUtil; import com.owncloud.android.utils.theme.ViewThemeUtils; @@ -83,7 +85,6 @@ import androidx.annotation.Nullable; import androidx.core.content.res.ResourcesCompat; import androidx.fragment.app.FragmentManager; -import androidx.viewpager2.widget.ViewPager2; /** * This Fragment is used to display the details about a file. @@ -92,6 +93,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener, private static final String TAG = FileDetailFragment.class.getSimpleName(); private static final String FTAG_CONFIRMATION = "REMOVE_CONFIRMATION_FRAGMENT"; static final String FTAG_RENAME_FILE = "RENAME_FILE_FRAGMENT"; + private static final String FTAG_SHARING = "SHARING_DETAILS_FRAGMENT"; private static final String ARG_FILE = "FILE"; private static final String ARG_PARENT_FOLDER = "PARENT_FOLDER"; @@ -114,6 +116,8 @@ public class FileDetailFragment extends FileFragment implements OnClickListener, @Inject FileDataStorageManager storageManager; @Inject ViewThemeUtils viewThemeUtils; @Inject BackgroundJobManager backgroundJobManager; + @Inject EditorUtils editorUtils; + @Inject SyncedFolderProvider syncedFolderProvider; /** * Public factory method to create new FileDetailFragment instances. @@ -175,12 +179,7 @@ public FileDetailSharingFragment getFileDetailSharingFragment() { if (binding == null) { return null; } - - if (binding.pager.getAdapter() instanceof FileDetailTabAdapter adapter) { - return adapter.getFileDetailSharingFragment(); - } - - return null; + return (FileDetailSharingFragment)requireActivity().getSupportFragmentManager().findFragmentByTag(FTAG_SHARING); } /** @@ -189,10 +188,6 @@ public FileDetailSharingFragment getFileDetailSharingFragment() { * @return reference to the {@link FileDetailActivitiesFragment} */ public FileDetailActivitiesFragment getFileDetailActivitiesFragment() { - if (binding.pager.getAdapter() instanceof FileDetailTabAdapter adapter) { - return adapter.getFileDetailActivitiesFragment(); - } - return null; } @@ -285,12 +280,11 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat } } - @Override - public void onDestroy() { - if (getActivity() instanceof DrawerActivity drawerActivity) { - drawerActivity.showBottomNavigationBar(true); - } - super.onDestroy(); + private void replaceSharingFragment() { + requireActivity().getSupportFragmentManager().beginTransaction() + .replace(R.id.sharing_frame_container, + FileDetailSharingFragment.newInstance(getFile(), user), + FTAG_SHARING).commit(); } private void onOverflowIconClicked() { @@ -302,87 +296,6 @@ private void onOverflowIconClicked() { .show(fragmentManager, "actions"); } - private void setupViewPager() { - binding.tabLayout.removeAllTabs(); - - binding.tabLayout.addTab( - binding - .tabLayout - .newTab() - .setText(R.string.drawer_item_activities) - .setIcon(R.drawable.selector_tab_activities) - ); - - if (showSharingTab()) { - binding.tabLayout.addTab(binding.tabLayout.newTab().setText(R.string.share_dialog_title).setIcon(R.drawable.selector_tab_share)); - } - - if (MimeTypeUtil.isImage(getFile())) { - binding.tabLayout.addTab(binding.tabLayout.newTab().setText(R.string.filedetails_details).setIcon(R.drawable.selector_media)); - } - - viewThemeUtils.material.themeTabLayout(binding.tabLayout); - - final FileDetailTabAdapter adapter = new FileDetailTabAdapter(requireActivity(), - getFile(), - user, - showSharingTab()); - binding.pager.setAdapter(adapter); - - binding.pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - final FileDetailActivitiesFragment fragment = getFileDetailActivitiesFragment(); - if (activeTab == 0 && fragment != null) { - fragment.markCommentsAsRead(); - } - super.onPageScrolled(position, positionOffset, positionOffsetPixels); - } - - @Override - public void onPageSelected(int position) { - super.onPageSelected(position); - if (binding != null) { - final var tab = binding.tabLayout.getTabAt(position); - if (tab != null) { - tab.select(); - } - } - } - }); - - binding.tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { - @Override - public void onTabSelected(TabLayout.Tab tab) { - binding.pager.setCurrentItem(tab.getPosition()); - if (tab.getPosition() == 0) { - final FileDetailActivitiesFragment fragment = getFileDetailActivitiesFragment(); - if (fragment != null) { - fragment.markCommentsAsRead(); - } - } - } - - @Override - public void onTabUnselected(TabLayout.Tab tab) { - // unused at the moment - } - - @Override - public void onTabReselected(TabLayout.Tab tab) { - // unused at the moment - } - }); - - binding.tabLayout.post(() -> { - if (binding != null) { - TabLayout.Tab tab = binding.tabLayout.getTabAt(activeTab); - if (tab == null) return; - tab.select(); - } - }); - } - @Override public void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); @@ -592,7 +505,8 @@ public void updateFileDetails(boolean transferring, boolean refresh) { fabMain.hide(); } - binding.syncBlock.setVisibility(file.isFolder() ? View.VISIBLE : View.GONE); + // NMC: not required + /* binding.syncBlock.setVisibility(file.isFolder() ? View.VISIBLE : View.GONE); if (file.isInternalFolderSync()) { binding.folderSyncButton.setChecked(file.isInternalFolderSync()); @@ -601,10 +515,13 @@ public void updateFileDetails(boolean transferring, boolean refresh) { binding.folderSyncButton.setChecked(true); binding.folderSyncButton.setEnabled(false); } - } + }*/ } - setupViewPager(); + // TODO: 06/21/23 remove this condition after Comments section included + if (SendShareDialog.isPeopleShareClicked) { + replaceSharingFragment(); + } final var view = getView(); if (view != null) { view.invalidate(); @@ -645,13 +562,19 @@ private void setFileModificationTimestamp(OCFile file, boolean showDetailedTimes private void setFavoriteIconStatus(boolean isFavorite) { if (isFavorite) { - binding.favorite.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.ic_star, null)); + binding.favorite.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.favorite, null)); binding.favorite.setContentDescription(getString(R.string.unset_favorite)); } else { binding.favorite.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.ic_star_outline, null)); binding.favorite.setContentDescription(getString(R.string.favorite)); + + //NMC Customization + binding.favorite.getDrawable().mutate().setColorFilter(requireContext() + .getResources() + .getColor(R.color.list_item_lastmod_and_filesize_text, null), + PorterDuff.Mode.SRC_IN); } } @@ -832,32 +755,14 @@ public void initiateSharingProcess(String shareeName, return; } - final var fileShareDetailFragment = FileDetailsSharingProcessFragment.newInstance(file, shareeName, shareType, secureShare); + final var fileShareDetailFragment = FileDetailsSharingProcessFragment.newInstance(file, shareeName, shareType, secureShare, SharePermissionManager.canEditFile(user, storageManager.getCapability(user), getFile(), editorUtils)); requireActivity() .getSupportFragmentManager() .beginTransaction() - .add(R.id.sharing_frame_container, fileShareDetailFragment, FileDetailsSharingProcessFragment.TAG) + .replace(R.id.sharing_frame_container, fileShareDetailFragment, FileDetailsSharingProcessFragment.TAG) + .addToBackStack(null) .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(); - } } /** @@ -868,11 +773,12 @@ public void showHideFragmentView(boolean isFragmentReplaced) { * @param isReshareShown */ public void editExistingShare(OCShare share, int screenTypePermission, boolean isReshareShown) { - requireActivity().getSupportFragmentManager().beginTransaction().add(R.id.sharing_frame_container, - FileDetailsSharingProcessFragment.newInstance(share, screenTypePermission, isReshareShown), + requireActivity().getSupportFragmentManager().beginTransaction().replace(R.id.sharing_frame_container, + FileDetailsSharingProcessFragment.newInstance(share, screenTypePermission, isReshareShown, + SharePermissionManager.canEditFile(user, storageManager.getCapability(user), getFile(), editorUtils)), FileDetailsSharingProcessFragment.TAG) + .addToBackStack(null) .commit(); - showHideFragmentView(true); } @Subscribe(threadMode = ThreadMode.BACKGROUND) @@ -897,21 +803,14 @@ public void onMessageEvent(FavoriteEvent event) { } } - private boolean showSharingTab() { - if (!MDMConfig.INSTANCE.shareViaLink(requireContext()) && !MDMConfig.INSTANCE.shareViaUser(requireContext())) { - return false; - } - - if (getFile().isEncrypted()) { - if (parentFolder == null) { - parentFolder = storageManager.getFileById(getFile().getParentId()); - } - // sharing not allowed for encrypted files, thus only show first tab (activities) - // sharing not allowed for encrypted subfolders - return EncryptionUtils.supportsSecureFiledrop(getFile(), user) && !parentFolder.isEncrypted(); - } else { - // unencrypted files/folders - return true; - } + /** + * hide the view for landscape mode to have more space for the user to type in search view + * {@link FileDetailSharingFragment#scrollToSearchViewPosition(boolean)} + * + * @param event + */ + @Subscribe(threadMode = ThreadMode.MAIN) + public void onMessageEvent(ShareSearchViewFocusEvent event) { + binding.shareDetailFileContainer.setVisibility(event.getHasFocus() ? View.GONE : View.VISIBLE); } } diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java index 969b29c8d349..cc2c668c8e7c 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java @@ -16,17 +16,20 @@ import android.Manifest; import android.accounts.AccountManager; +import android.annotation.SuppressLint; import android.app.Activity; import android.app.SearchManager; import android.content.Context; import android.content.Intent; +import android.content.res.Configuration; import android.database.Cursor; +import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.provider.ContactsContract; -import android.text.InputType; import android.text.TextUtils; +import android.text.style.StyleSpan; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -34,21 +37,20 @@ import android.view.animation.AnimationUtils; import android.widget.LinearLayout; +import com.google.android.material.appbar.AppBarLayout; import com.nextcloud.client.account.User; import com.nextcloud.client.account.UserAccountManager; import com.nextcloud.client.database.entity.FileEntity; import com.nextcloud.client.di.Injectable; import com.nextcloud.client.network.ClientFactory; +import com.nextcloud.utils.EditorUtils; import com.nextcloud.utils.extensions.BundleExtensionsKt; import com.nextcloud.utils.extensions.FileExtensionsKt; -import com.nextcloud.utils.extensions.OCShareExtensionsKt; -import com.nextcloud.utils.extensions.ViewExtensionsKt; -import com.nextcloud.utils.mdm.MDMConfig; +import com.nmc.android.utils.SearchViewThemeUtils; import com.owncloud.android.R; import com.owncloud.android.databinding.FileDetailsSharingFragmentBinding; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.datamodel.SharesType; import com.owncloud.android.lib.common.OwnCloudAccount; import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.utils.Log_OC; @@ -65,13 +67,17 @@ import com.owncloud.android.ui.dialog.SharePasswordDialogFragment; import com.owncloud.android.ui.fragment.share.RemoteShareRepository; import com.owncloud.android.ui.fragment.share.ShareRepository; +import com.owncloud.android.ui.events.ShareSearchViewFocusEvent; import com.owncloud.android.ui.fragment.util.FileDetailSharingFragmentHelper; +import com.owncloud.android.ui.fragment.util.SharePermissionManager; import com.owncloud.android.ui.helpers.FileOperationsHelper; import com.owncloud.android.utils.ClipboardUtil; import com.owncloud.android.utils.DisplayUtils; import com.owncloud.android.utils.PermissionUtil; import com.owncloud.android.utils.theme.ViewThemeUtils; +import org.greenrobot.eventbus.EventBus; + import java.util.ArrayList; import java.util.List; @@ -107,13 +113,16 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda private FileDetailsSharingFragmentBinding binding; private OnEditShareListener onEditShareListener; - - private ShareeListAdapter internalShareeListAdapter; - - private ShareeListAdapter externalShareeListAdapter; + + private ShareeListAdapter linkShareeListAdapter; + + private ShareeListAdapter emailShareeListAdapter; + + private boolean isSearchViewFocused; @Inject UserAccountManager accountManager; @Inject ClientFactory clientFactory; + @Inject EditorUtils editorUtils; @Inject ViewThemeUtils viewThemeUtils; @Inject UsersAndGroupsSearchConfig searchConfig; @@ -157,7 +166,6 @@ public void onCreate(@Nullable Bundle savedInstanceState) { } fileDataStorageManager = fileActivity.getStorageManager(); - fetchSharees(); } private void fetchSharees() { @@ -201,10 +209,14 @@ private void showShareContainer() { binding.shareContainer.setVisibility(View.VISIBLE); } + @SuppressLint("ClickableViewAccessibility") @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { binding = FileDetailsSharingFragmentBinding.inflate(inflater, container, false); + // NMC: call fetch shares here because we are replacing the fragments + // instead of adding them where we have to fetch shares again. + fetchSharees(); final Animation blinkAnimation = AnimationUtils.loadAnimation(requireContext(), R.anim.blink); binding.shimmerLayout.getRoot().startAnimation(blinkAnimation); @@ -214,37 +226,44 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, String userId = accountManager.getUserData(user.toPlatformAccount(), com.owncloud.android.lib.common.accounts.AccountUtils.Constants.KEY_USER_ID); - internalShareeListAdapter = new ShareeListAdapter(fileActivity, + linkShareeListAdapter = new ShareeListAdapter(fileActivity, new ArrayList<>(), this, userId, user, viewThemeUtils, - file.isEncrypted(), - SharesType.INTERNAL); + file.isEncrypted()); + linkShareeListAdapter.setHasStableIds(true); - internalShareeListAdapter.setHasStableIds(true); + binding.linkSharesList.setAdapter(linkShareeListAdapter); - binding.sharesListInternal.setAdapter(internalShareeListAdapter); + binding.linkSharesList.setLayoutManager(new LinearLayoutManager(requireContext())); - binding.sharesListInternal.setLayoutManager(new LinearLayoutManager(requireContext())); + emailShareeListAdapter = new ShareeListAdapter(fileActivity, + new ArrayList<>(), + this, + userId, + user, + viewThemeUtils, + file.isEncrypted()); + emailShareeListAdapter.setHasStableIds(true); - externalShareeListAdapter = new ShareeListAdapter(fileActivity, - new ArrayList<>(), - this, - userId, - user, - viewThemeUtils, - file.isEncrypted(), - SharesType.EXTERNAL); + binding.sharesList.setAdapter(emailShareeListAdapter); - externalShareeListAdapter.setHasStableIds(true); + binding.sharesList.setLayoutManager(new LinearLayoutManager(requireContext())); - binding.sharesListExternal.setAdapter(externalShareeListAdapter); + binding.pickContactEmailBtn.setOnClickListener(v -> checkContactPermission()); - binding.sharesListExternal.setLayoutManager(new LinearLayoutManager(requireContext())); + binding.shareCreateNewLink.setOnClickListener(v -> createPublicShareLink()); - binding.pickContactEmailBtn.setOnClickListener(v -> checkContactPermission()); + //remove focus from search view on click of root view + binding.shareContainer.setOnClickListener(v -> binding.searchView.clearFocus()); + + //enable-disable scrollview scrolling + binding.fileDetailsNestedScrollView.setOnTouchListener((view1, motionEvent) -> { + //true means disable the scrolling and false means enable the scrolling + return com.nmc.android.utils.DisplayUtils.isLandscapeOrientation() && isSearchViewFocused; + }); setupView(); @@ -285,68 +304,75 @@ public void onStop() { private void setupView() { setShareWithYou(); + } - OCFile parentFile = fileDataStorageManager.getFileById(file.getParentId()); - + private void setUpSearchView() { FileDetailSharingFragmentHelper.setupSearchView( (SearchManager) fileActivity.getSystemService(Context.SEARCH_SERVICE), binding.searchView, fileActivity.getComponentName()); - viewThemeUtils.androidx.themeToolbarSearchView(binding.searchView); - viewThemeUtils.material.colorMaterialTextButton(binding.sharesListInternalShowAll); - binding.sharesListInternalShowAll.setOnClickListener(view -> { - internalShareeListAdapter.toggleShowAll(); - int textRes = internalShareeListAdapter.isShowAll() ? R.string.show_less : R.string.show_all; - binding.sharesListInternalShowAll.setText(textRes); - }); + SearchViewThemeUtils.INSTANCE.themeSearchView(requireContext(), binding.searchView); - viewThemeUtils.material.colorMaterialTextButton(binding.sharesListExternalShowAll); - binding.sharesListExternalShowAll.setOnClickListener(view -> { - externalShareeListAdapter.toggleShowAll(); - int textRes = externalShareeListAdapter.isShowAll() ? R.string.show_less : R.string.show_all; - binding.sharesListExternalShowAll.setText(textRes); + binding.searchView.setQueryHint(getResources().getString(R.string.share_search)); + binding.searchView.setVisibility(View.VISIBLE); + binding.pickContactEmailBtn.setVisibility(View.VISIBLE); + + binding.searchView.setOnQueryTextFocusChangeListener((view, hasFocus) -> { + isSearchViewFocused = hasFocus; + scrollToSearchViewPosition(false); }); - if (file.canReshare() && !FileDetailSharingFragmentHelper.isPublicShareDisabled(capabilities)) { - if (file.isEncrypted() || (parentFile != null && parentFile.isEncrypted())) { - if (file.getE2eCounter() == -1) { - // V1 cannot share - binding.searchContainer.setVisibility(View.GONE); - binding.createLink.setVisibility(View.GONE); - } else { - binding.createLink.setText(R.string.add_new_secure_file_drop); - binding.searchView.setQueryHint(getResources().getString(R.string.secure_share_search)); + } - if (file.isSharedViaLink()) { - binding.searchView.setQueryHint(getResources().getString(R.string.share_not_allowed_when_file_drop)); - binding.searchView.setInputType(InputType.TYPE_NULL); - disableSearchView(binding.searchView); + /** + * @param isDeviceRotated true when user rotated the device and false when user is already in landscape mode + */ + private void scrollToSearchViewPosition(boolean isDeviceRotated) { + if (com.nmc.android.utils.DisplayUtils.isLandscapeOrientation()) { + if (isSearchViewFocused) { + binding.fileDetailsNestedScrollView.post(() -> { + //ignore the warning because there can be case that the scrollview can be null + if (binding.fileDetailsNestedScrollView == null) { + return; } - } + + //need to hide app bar to have more space in landscape mode while search view is focused + hideAppBar(); + + //send the event to hide the share top view to have more space + //need to use this here else white view will be visible for sometime + EventBus.getDefault().post(new ShareSearchViewFocusEvent(isSearchViewFocused)); + + if (isDeviceRotated) { + //during the rotation we need to use getTop() method for proper alignment of search view + //-25 just to avoid blank space at top + binding.fileDetailsNestedScrollView.smoothScrollTo(0, binding.searchView.getTop() - 20); + } else { + //when user is already in landscape mode and search view gets focus + //we need to user getBottom() method for proper alignment of search view + //-100 just to avoid blank space at top + binding.fileDetailsNestedScrollView.smoothScrollTo(0, binding.searchView.getBottom() - 100); + } + }); } else { - binding.createLink.setText(R.string.create_link); - binding.searchView.setQueryHint(getResources().getString(R.string.share_search_internal)); + //send the event to show the share top view again + EventBus.getDefault().post(new ShareSearchViewFocusEvent(isSearchViewFocused)); } - - binding.createLink.setOnClickListener(v -> createPublicShareLink()); - } else { - binding.searchView.setQueryHint(getResources().getString(R.string.resharing_is_not_allowed)); - binding.createLink.setVisibility(View.GONE); - binding.externalSharesHeadline.setVisibility(View.GONE); - binding.searchView.setInputType(InputType.TYPE_NULL); - binding.pickContactEmailBtn.setVisibility(View.GONE); - disableSearchView(binding.searchView); - binding.createLink.setOnClickListener(null); + //in portrait mode we need to see the layout everytime + //send the event to show the share top view + EventBus.getDefault().post(new ShareSearchViewFocusEvent(false)); } - - checkShareViaUser(); } - private void checkShareViaUser() { - if (!MDMConfig.INSTANCE.shareViaUser(requireContext())) { - binding.searchContainer.setVisibility(View.GONE); + private void hideAppBar() { + if (requireActivity() instanceof FileDisplayActivity) { + AppBarLayout appBarLayout = requireActivity().findViewById(R.id.appbar); + + if (appBarLayout != null) { + appBarLayout.setExpanded(false, true); + } } } @@ -360,13 +386,52 @@ private void disableSearchView(View view) { } } + /** + * will be called from FileActivity when user is sharing from PreviewImageFragment + * + * @param shareeName + * @param shareType + */ + public void initiateSharingProcess(String shareeName, ShareType shareType, boolean secureShare) { + requireActivity().getSupportFragmentManager().beginTransaction().replace(R.id.share_fragment_container, + FileDetailsSharingProcessFragment.newInstance(file, + shareeName, + shareType, + secureShare, + SharePermissionManager.canEditFile(user, capabilities, file, editorUtils)), + FileDetailsSharingProcessFragment.TAG) + .addToBackStack(null) + .commit(); + } + + /** + * open the new sharing screen process to modify the created share this will be called from PreviewImageFragment + * + * @param share + * @param screenTypePermission + * @param isReshareShown + */ + public void editExistingShare(OCShare share, int screenTypePermission, boolean isReshareShown) { + requireActivity().getSupportFragmentManager().beginTransaction().replace(R.id.share_fragment_container, + FileDetailsSharingProcessFragment.newInstance(share, screenTypePermission, isReshareShown, + SharePermissionManager.canEditFile(user, capabilities, file, editorUtils)), + FileDetailsSharingProcessFragment.TAG) + .addToBackStack(null) + .commit(); + } + private void setShareWithYou() { + setUpSearchView(); if (accountManager.userOwnsFile(file, user)) { - binding.sharedWithYouContainer.setVisibility(View.GONE); + binding.tvResharingInfo.setVisibility(View.GONE); + binding.tvResharingStatus.setVisibility(View.GONE); } else { - binding.sharedWithYouUsername.setText( - String.format(getString(R.string.shared_with_you_by), file.getOwnerDisplayName())); - DisplayUtils.setAvatar(user, + binding.tvResharingInfo.setText( + DisplayUtils.createTextWithSpan( + String.format(getString(R.string.resharing_user_info), file.getOwnerDisplayName()), + file.getOwnerDisplayName(), + new StyleSpan(Typeface.BOLD))); + /* DisplayUtils.setAvatar(user, file.getOwnerId(), this, getResources().getDimension( @@ -374,16 +439,27 @@ private void setShareWithYou() { getResources(), binding.sharedWithYouAvatar, getContext()); - binding.sharedWithYouAvatar.setVisibility(View.VISIBLE); - - String note = file.getNote(); + binding.sharedWithYouAvatar.setVisibility(View.VISIBLE);*/ - if (!TextUtils.isEmpty(note)) { - binding.sharedWithYouNote.setText(file.getNote()); - binding.sharedWithYouNoteContainer.setVisibility(View.VISIBLE); + if (file.canReshare()) { + binding.tvResharingStatus.setText(getResources().getString(R.string.reshare_allowed)); } else { - binding.sharedWithYouNoteContainer.setVisibility(View.GONE); + binding.orSectionLayout.setVisibility(View.GONE); + binding.linkShareSectionHeading.setVisibility(View.GONE); + binding.linkSharesList.setVisibility(View.GONE); + binding.shareCreateNewLink.setVisibility(View.GONE); + + binding.sharedWithDivider.setVisibility(View.GONE); + binding.tvYourShares.setVisibility(View.GONE); + binding.sharesList.setVisibility(View.GONE); + binding.tvEmptyShares.setVisibility(View.GONE); + + binding.tvResharingStatus.setText(getResources().getString(R.string.reshare_not_allowed)); + + disableSearchView(binding.searchContainer); } + binding.tvResharingStatus.setVisibility(View.VISIBLE); + binding.tvResharingInfo.setVisibility(View.VISIBLE); } } @@ -439,6 +515,8 @@ public void copyLink(OCShare share) { } else { ClipboardUtil.copyToClipboard(requireActivity(), share.getShareLink()); } + // NMC: send link after copying it to clipboard + sendLink(share); } } @@ -479,8 +557,8 @@ public void onUpdateShareInformation(RemoteOperationResult result, OCFile file) } /** - * Updates the UI after the result of an update operation on the edited {@link OCFile}. Keeps the current {@link - * OCFile held by this fragment}. + * Updates the UI after the result of an update operation on the edited {@link OCFile}. Keeps the current + * {@link OCFile held by this fragment}. * * @param result {@link RemoteOperationResult} of an update on the edited {@link OCFile} sharing information. * @see #onUpdateShareInformation(RemoteOperationResult, OCFile) @@ -550,54 +628,70 @@ public void refreshCapabilitiesFromDB() { */ @SuppressFBWarnings("PSC") public void refreshSharesFromDB() { + // NMC-4582 NPE fix + if (binding == null) { + return; + } + OCFile newFile = fileDataStorageManager.getFileById(file.getFileId()); if (newFile != null) { file = newFile; } - if (internalShareeListAdapter == null) { + if (emailShareeListAdapter == null) { DisplayUtils.showSnackMessage(getView(), getString(R.string.could_not_retrieve_shares)); return; } + emailShareeListAdapter.removeAll(); - internalShareeListAdapter.removeAll(); + /*//update flag in adapter + adapter.setTextFile(SharePermissionManager.canEditFile(user, + capabilities, file, editorUtils));*/ // to show share with users/groups info List shares = fileDataStorageManager.getSharesWithForAFile(file.getRemotePath(), user.getAccountName()); - List internalShares = new ArrayList<>(); - List externalShares = new ArrayList<>(); - - for (OCShare share : shares) { - if (share.getShareType() != null) { - switch (share.getShareType()) { - case PUBLIC_LINK: - case FEDERATED_GROUP: - case FEDERATED: - case EMAIL: - externalShares.add(share); - break; - - default: - internalShares.add(share); - break; - } - } + emailShareeListAdapter.addShares(shares); + + showHideEmailShareView(shares.isEmpty()); + + if (FileDetailSharingFragmentHelper.isPublicShareDisabled(capabilities) || !file.canReshare()) { + return; } - - internalShareeListAdapter.addShares(internalShares); - ViewExtensionsKt.setVisibleIf(binding.sharesListInternalShowAll, internalShareeListAdapter.shares.size() > 3); - addExternalAndPublicShares(externalShares); - ViewExtensionsKt.setVisibleIf(binding.sharesListExternalShowAll, externalShareeListAdapter.shares.size() > 3); + if (linkShareeListAdapter == null) { + DisplayUtils.showSnackMessage(getView(), getString(R.string.could_not_retrieve_shares)); + return; + } + linkShareeListAdapter.removeAll(); + + /*//update flag in adapter + linkAdapter.setTextFile(SharePermissionManager.canEditFile(user, + capabilities, file, editorUtils));*/ + + // Get public share + List publicShares = fileDataStorageManager.getSharesByPathAndType(file.getRemotePath(), + ShareType.PUBLIC_LINK, + ""); + + linkShareeListAdapter.addShares(publicShares); + + showHideLinkShareView(publicShares == null || publicShares.isEmpty()); } - private void addExternalAndPublicShares(List externalShares) { - final var publicShares = fileDataStorageManager.getSharesByPathAndType(file.getRemotePath(), ShareType.PUBLIC_LINK, ""); - externalShareeListAdapter.removeAll(); - final var shares = OCShareExtensionsKt.mergeDistinctByToken(externalShares, publicShares); - externalShareeListAdapter.addShares(shares); + private void showHideLinkShareView(boolean isEmptyList) { + binding.linkSharesList.setVisibility(isEmptyList ? View.GONE : View.VISIBLE); + } + + private void showHideEmailShareView(boolean isEmptyList) { + binding.sharesList.setVisibility(isEmptyList ? View.GONE : View.VISIBLE); + // additional check to hide the empty shares if file cannot be shared + if (!file.canReshare()) { + binding.tvEmptyShares.setVisibility(View.GONE); + return; + } + binding.tvEmptyShares.setVisibility(isEmptyList ? View.VISIBLE : View.GONE); } private void checkContactPermission() { @@ -661,7 +755,8 @@ public void onSaveInstanceState(@NonNull Bundle outState) { @Override public void avatarGenerated(Drawable avatarDrawable, Object callContext) { - binding.sharedWithYouAvatar.setImageDrawable(avatarDrawable); + // NMC: not required + // binding.sharedWithYouAvatar.setImageDrawable(avatarDrawable); } @Override @@ -680,6 +775,11 @@ public void search(String query) { searchView.setQuery(query, true); } + @Override + public void openIn(OCShare share) { + fileOperationsHelper.sendShareFile(file, true); + } + @Override public void advancedPermissions(OCShare share) { modifyExistingShare(share, FileDetailsSharingProcessFragment.SCREEN_TYPE_PERMISSION); @@ -696,16 +796,16 @@ public void unShare(OCShare share) { FileEntity entity = fileDataStorageManager.getFileEntity(file); - if (binding.sharesListInternal.getAdapter() instanceof ShareeListAdapter adapter) { + if (binding.linkSharesList.getAdapter() instanceof ShareeListAdapter adapter) { adapter.remove(share); if (entity != null && adapter.isAdapterEmpty()) { - entity.setSharedWithSharee(0); + entity.setSharedViaLink(0); fileDataStorageManager.updateFileEntity(entity); } - } else if (binding.sharesListExternal.getAdapter() instanceof ShareeListAdapter adapter) { + } else if (binding.sharesList.getAdapter() instanceof ShareeListAdapter adapter) { adapter.remove(share); if (entity != null && adapter.isAdapterEmpty()) { - entity.setSharedViaLink(0); + entity.setSharedWithSharee(0); fileDataStorageManager.updateFileEntity(entity); } } else { @@ -722,11 +822,6 @@ public void sendLink(OCShare share) { } } - @Override - public void addAnotherLink(OCShare share) { - createPublicShareLink(); - } - private void modifyExistingShare(OCShare share, int screenTypePermission) { onEditShareListener.editExistingShare(share, screenTypePermission, !isReshareForbidden(share)); } @@ -736,11 +831,6 @@ public void onQuickPermissionChanged(OCShare share, int permission) { fileOperationsHelper.setPermissionsToShare(share, permission); } - @Override - public void openShareDetailWithCustomPermissions(OCShare share) { - modifyExistingShare(share, FileDetailsSharingProcessFragment.SCREEN_TYPE_PERMISSION_WITH_CUSTOM_PERMISSION); - } - //launcher for contact permission private final ActivityResultLauncher requestContactPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { @@ -777,5 +867,16 @@ public interface OnEditShareListener { void editExistingShare(OCShare share, int screenTypePermission, boolean isReshareShown); void onShareProcessClosed(); + + void onLinkShareDownloadLimitFetched(long downloadLimit, long downloadCount); + } + + @Override + public void onConfigurationChanged(@NonNull Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + //when user is in portrait mode and search view is focused and keyboard is open + //so when user rotate the device we have to fix the search view properly in landscape mode + scrollToSearchViewPosition(true); } } diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingMenuBottomSheetDialog.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingMenuBottomSheetDialog.java index 86b21f332872..0a08d6f50a78 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingMenuBottomSheetDialog.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingMenuBottomSheetDialog.java @@ -57,13 +57,6 @@ protected void onCreate(Bundle savedInstanceState) { getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); } - viewThemeUtils.platform.themeDialog(binding.getRoot()); - - viewThemeUtils.platform.colorImageView(binding.menuIconAdvancedPermissions, ColorRole.PRIMARY); - viewThemeUtils.platform.colorImageView(binding.menuIconSendLink, ColorRole.PRIMARY); - viewThemeUtils.platform.colorImageView(binding.menuIconUnshare, ColorRole.PRIMARY); - viewThemeUtils.platform.colorImageView(binding.menuIconSendNewEmail, ColorRole.PRIMARY); - updateUI(); setupClickListener(); @@ -75,12 +68,18 @@ protected void onCreate(Bundle savedInstanceState) { } private void updateUI() { + if (ocShare.isFolder()) { + binding.menuShareOpenIn.setVisibility(View.GONE); + } else { + binding.menuShareOpenIn.setVisibility(View.VISIBLE); + } + if (ocShare.getShareType() == ShareType.PUBLIC_LINK) { if (MDMConfig.INSTANCE.sendFilesSupport(getContext())) { - binding.menuShareSendLink.setVisibility(View.VISIBLE); + binding.menuShareSendNewEmail.setVisibility(View.GONE); } } else { - binding.menuShareSendLink.setVisibility(View.GONE); + binding.menuShareSendNewEmail.setVisibility(View.VISIBLE); } if (SharePermissionManager.INSTANCE.isSecureFileDrop(ocShare) && encrypted) { @@ -89,6 +88,11 @@ private void updateUI() { } private void setupClickListener() { + binding.menuShareOpenIn.setOnClickListener(v -> { + actions.openIn(ocShare); + dismiss(); + }); + binding.menuShareAdvancedPermissions.setOnClickListener(v -> { actions.advancedPermissions(ocShare); dismiss(); diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingMenuBottomSheetActions.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingMenuBottomSheetActions.java index 241eabb1f2a7..163b66329775 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingMenuBottomSheetActions.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingMenuBottomSheetActions.java @@ -18,6 +18,10 @@ * 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 @@ -39,8 +43,4 @@ public interface FileDetailsSharingMenuBottomSheetActions { */ void sendLink(OCShare share); - /** - * create another link only valid for {@link ShareType#PUBLIC_LINK} - */ - void addAnotherLink(OCShare share); } diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt index d1d2327a51ed..3fbd3601efe0 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt @@ -10,18 +10,22 @@ package com.owncloud.android.ui.fragment import android.content.Context import android.content.res.Configuration import android.os.Bundle +import android.os.Handler +import android.os.Looper import android.text.TextUtils import android.view.LayoutInflater +import android.view.MotionEvent import android.view.View import android.view.ViewGroup -import androidx.core.view.isVisible +import android.view.inputmethod.EditorInfo +import android.widget.RadioGroup import androidx.fragment.app.Fragment import com.nextcloud.client.di.Injectable import com.nextcloud.utils.extensions.getParcelableArgument import com.nextcloud.utils.extensions.getSerializableArgument import com.nextcloud.utils.extensions.isPublicOrMail -import com.nextcloud.utils.extensions.setVisibilityWithAnimation import com.nextcloud.utils.extensions.setVisibleIf +import com.nmc.android.utils.CheckableThemeUtils import com.owncloud.android.R import com.owncloud.android.databinding.FileDetailsSharingProcessFragmentBinding import com.owncloud.android.datamodel.OCFile @@ -40,6 +44,7 @@ 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.CapabilityUtils +import com.owncloud.android.utils.KeyboardUtils import com.owncloud.android.utils.theme.ViewThemeUtils import java.text.SimpleDateFormat import java.util.Date @@ -57,7 +62,8 @@ import javax.inject.Inject class FileDetailsSharingProcessFragment : Fragment(), Injectable, - ExpirationDatePickerDialogFragment.OnExpiryDateListener { + ExpirationDatePickerDialogFragment.OnExpiryDateListener, + RadioGroup.OnCheckedChangeListener { companion object { const val TAG = "FileDetailsSharingProcessFragment" @@ -68,11 +74,11 @@ class FileDetailsSharingProcessFragment : private const val ARG_SCREEN_TYPE = "arg_screen_type" private const val ARG_RESHARE_SHOWN = "arg_reshare_shown" private const val ARG_SECURE_SHARE = "secure_share" + private const val ARG_IS_TEXT_FILE = "is_text_file" // types of screens to be displayed const val SCREEN_TYPE_PERMISSION = 1 // permissions screen const val SCREEN_TYPE_NOTE = 2 // note screen - const val SCREEN_TYPE_PERMISSION_WITH_CUSTOM_PERMISSION = 3 // permissions screen with custom permission /** * fragment instance to be called while creating new share for internal and external share @@ -82,13 +88,15 @@ class FileDetailsSharingProcessFragment : file: OCFile, shareeName: String, shareType: ShareType, - secureShare: Boolean + secureShare: Boolean, + isTextFile: Boolean ): FileDetailsSharingProcessFragment { val bundle = Bundle().apply { putParcelable(ARG_OCFILE, file) putSerializable(ARG_SHARE_TYPE, shareType) putString(ARG_SHAREE_NAME, shareeName) putBoolean(ARG_SECURE_SHARE, secureShare) + putBoolean(ARG_IS_TEXT_FILE, isTextFile) } return FileDetailsSharingProcessFragment().apply { @@ -100,11 +108,17 @@ class FileDetailsSharingProcessFragment : * fragment instance to be called while modifying existing share information */ @JvmStatic - fun newInstance(share: OCShare, screenType: Int, isReshareShown: Boolean): FileDetailsSharingProcessFragment { + fun newInstance( + share: OCShare, + screenType: Int, + isReshareShown: Boolean, + isTextFile: Boolean + ): FileDetailsSharingProcessFragment { val bundle = Bundle().apply { putParcelable(ARG_OCSHARE, share) putInt(ARG_SCREEN_TYPE, screenType) putBoolean(ARG_RESHARE_SHOWN, isReshareShown) + putBoolean(ARG_IS_TEXT_FILE, isTextFile) } return FileDetailsSharingProcessFragment().apply { @@ -116,6 +130,9 @@ class FileDetailsSharingProcessFragment : @Inject lateinit var viewThemeUtils: ViewThemeUtils + @Inject + lateinit var keyboardUtils: KeyboardUtils + private lateinit var onEditShareListener: FileDetailSharingFragment.OnEditShareListener private lateinit var binding: FileDetailsSharingProcessFragmentBinding @@ -128,16 +145,20 @@ class FileDetailsSharingProcessFragment : 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 isTextFile: Boolean = false private var share: OCShare? = null private var isReShareShown: Boolean = true // show or hide reShare option private var isSecureShare: Boolean = false + private var isDownloadCountFetched: Boolean = false private lateinit var capabilities: OCCapability private var expirationDatePickerFragment: ExpirationDatePickerDialogFragment? = null private var downloadAttribute: String? = null + private var downloadLimit: String? = null + override fun onAttach(context: Context) { super.onAttach(context) try { @@ -156,9 +177,8 @@ class FileDetailsSharingProcessFragment : requireNotNull(fileActivity) { "FileActivity may not be null" } - permission = share?.permissions - ?: capabilities.defaultPermissions - ?: SharePermissionManager.getMaximumPermission(isFolder()) + // NMC customization: default read only permission + permission = share?.permissions ?: OCShare.READ_PERMISSION_FLAG } private fun initArguments() { @@ -176,6 +196,17 @@ class FileDetailsSharingProcessFragment : shareProcessStep = it.getInt(ARG_SCREEN_TYPE, SCREEN_TYPE_PERMISSION) isReShareShown = it.getBoolean(ARG_RESHARE_SHOWN, true) isSecureShare = it.getBoolean(ARG_SECURE_SHARE, false) + isTextFile = it.getBoolean(ARG_IS_TEXT_FILE, false) + } + } + + // Updating Hide Download enable/disable on selection of FileDrop + override fun onCheckedChanged(group: RadioGroup?, checkId: Int) { + if (binding.fileDropRadioButton.id == checkId) { + binding.shareProcessHideDownloadCheckbox.isChecked = true + binding.shareProcessHideDownloadCheckbox.isEnabled = false + } else { + binding.shareProcessHideDownloadCheckbox.isEnabled = true } } @@ -189,15 +220,24 @@ class FileDetailsSharingProcessFragment : super.onViewCreated(view, savedInstanceState) if (isShareProcessStepIsPermission()) { setupUI() + setVisibilitiesOfShareOption() + toggleNextButtonAvailability(isAnySharePermissionChecked()) } else { + // NMC Customization: for note share directly enable button + toggleNextButtonAvailability(true) updateViewForNoteScreenType() } + //Set default value to 0 for download count + if (!isDownloadCountFetched) { + binding.shareProcessRemainingDownloadCountTv.text = + String.format(resources.getString(R.string.download_text), "0") + } + binding.shareRadioGroup.setOnCheckedChangeListener(this) implementClickEvents() + implementEditorListener() setCheckboxStates() themeView() - setVisibilitiesOfShareOption() - toggleNextButtonAvailability(isAnySharePermissionChecked()) logShareInfo() } @@ -214,57 +254,18 @@ class FileDetailsSharingProcessFragment : private fun setVisibilitiesOfShareOption() { binding.run { - shareAllowDownloadAndSyncCheckbox.setVisibleIf(!isPublicShare()) - fileRequestRadioButton.setVisibleIf(canSetFileRequest()) + shareProcessHideDownloadCheckbox.setVisibleIf(!isPublicShare()) + fileDropRadioButton.setVisibleIf(canSetFileRequest()) } } private fun themeView() { - viewThemeUtils.platform.run { - binding.run { - colorTextView(shareProcessEditShareLink) - colorTextView(shareCustomPermissionsText) - - themeRadioButton(viewOnlyRadioButton) - themeRadioButton(canEditRadioButton) - themeRadioButton(customPermissionRadioButton) - - if (!isPublicShare()) { - themeCheckbox(shareAllowDownloadAndSyncCheckbox) - } - - if (canSetFileRequest()) { - themeRadioButton(fileRequestRadioButton) - } - - themeCheckbox(shareReadCheckbox) - themeCheckbox(shareCreateCheckbox) - themeCheckbox(shareEditCheckbox) - themeCheckbox(shareCheckbox) - themeCheckbox(shareDeleteCheckbox) - } - } - - viewThemeUtils.androidx.run { - binding.run { - colorSwitchCompat(shareProcessSetPasswordSwitch) - colorSwitchCompat(shareProcessSetExpDateSwitch) - colorSwitchCompat(shareProcessSetDownloadLimitSwitch) - colorSwitchCompat(shareProcessHideDownloadCheckbox) - colorSwitchCompat(shareProcessChangeNameSwitch) - } - } - - viewThemeUtils.material.run { - binding.run { - colorTextInputLayout(shareProcessEnterPasswordContainer) - colorTextInputLayout(shareProcessSetDownloadLimitInputContainer) - colorTextInputLayout(shareProcessChangeNameContainer) - colorTextInputLayout(noteContainer) - colorMaterialButtonPrimaryFilled(shareProcessBtnNext) - colorMaterialButtonPrimaryOutlined(shareProcessBtnCancel) - } - } + CheckableThemeUtils.tintSwitch(binding.shareProcessSetPasswordSwitch) + CheckableThemeUtils.tintSwitch(binding.shareProcessAllowResharingCheckbox) + CheckableThemeUtils.tintSwitch(binding.shareProcessSetExpDateSwitch) + CheckableThemeUtils.tintSwitch(binding.shareProcessHideDownloadCheckbox) + CheckableThemeUtils.tintSwitch(binding.shareProcessChangeNameSwitch) + CheckableThemeUtils.tintSwitch(binding.shareProcessDownloadLimitSwitch) } override fun onConfigurationChanged(newConfig: Configuration) { @@ -282,8 +283,8 @@ class FileDetailsSharingProcessFragment : private fun setupUI() { binding.run { + viewOnlyRadioButton.isChecked = true shareProcessGroupOne.visibility = View.VISIBLE - shareProcessEditShareLink.visibility = View.VISIBLE shareProcessGroupTwo.visibility = View.GONE } @@ -291,6 +292,7 @@ class FileDetailsSharingProcessFragment : // show or hide expiry date binding.shareProcessSetExpDateSwitch.setVisibleIf(!isSecureShare) + binding.dividerSharingExpDate.setVisibleIf(!isSecureShare) shareProcessStep = SCREEN_TYPE_PERMISSION } @@ -302,21 +304,12 @@ class FileDetailsSharingProcessFragment : } } - private fun setMaxPermissionsIfDefaultPermissionExists() { - if (capabilities.defaultPermissions != null) { - binding.canEditRadioButton.isChecked = true - permission = SharePermissionManager.getMaximumPermission(isFolder()) - } - } - // region ViewUpdates private fun updateViewForCreate() { binding.shareProcessBtnNext.text = getString(R.string.common_next) updateViewAccordingToFile() showPasswordInput(binding.shareProcessSetPasswordSwitch.isChecked) showExpirationDateInput(binding.shareProcessSetExpDateSwitch.isChecked) - showFileDownloadLimitInput(binding.shareProcessSetDownloadLimitSwitch.isChecked) - setMaxPermissionsIfDefaultPermissionExists() } private fun updateViewAccordingToFile() { @@ -331,49 +324,19 @@ class FileDetailsSharingProcessFragment : } private fun updateViewForUpdate() { + binding.shareProcessBtnNext.text = requireContext().resources.getString(R.string.common_confirm) + if (share?.isFolder == true) updateViewForFolder() else updateViewForFile() selectRadioButtonAccordingToPermission() - if (isShareProcessStepIsCustomPermission()) { - selectCustomPermissionLayout() - } - shareType = share?.shareType ?: ShareType.NO_SHARED - // show different text for link share and other shares - // because we have link to share in Public Link - binding.shareProcessBtnNext.text = getString( - if (isPublicShare()) { - R.string.share_copy_link - } else { - R.string.common_confirm - } - ) - updateViewForShareType() binding.shareProcessSetPasswordSwitch.isChecked = share?.isPasswordProtected == true showPasswordInput(binding.shareProcessSetPasswordSwitch.isChecked) updateExpirationDateView() showExpirationDateInput(binding.shareProcessSetExpDateSwitch.isChecked) - updateFileDownloadLimitView() - showFileDownloadLimitInput(binding.shareProcessSetDownloadLimitSwitch.isChecked) - maskPasswordInput() - } - - private fun maskPasswordInput() { - if (share?.isPasswordProtected == false) { - return - } - - binding.shareProcessEnterPassword.run { - setText("••••••") - setOnFocusChangeListener { _, hasFocus -> - if (hasFocus) { - text?.clear() - } - } - } } private fun selectRadioButtonAccordingToPermission() { @@ -389,11 +352,7 @@ class FileDetailsSharingProcessFragment : } QuickPermissionType.FILE_REQUEST -> { - fileRequestRadioButton.isChecked = true - } - - QuickPermissionType.CUSTOM_PERMISSIONS -> { - selectCustomPermissionLayout() + fileDropRadioButton.isChecked = true } else -> Unit @@ -401,11 +360,6 @@ class FileDetailsSharingProcessFragment : } } - private fun selectCustomPermissionLayout() { - binding.customPermissionRadioButton.isChecked = true - binding.customPermissionLayout.setVisibilityWithAnimation(true) - } - private fun updateViewForShareType() { when (shareType) { ShareType.EMAIL -> { @@ -423,54 +377,98 @@ class FileDetailsSharingProcessFragment : } private fun updateViewForExternalShare() { - binding.run { - shareProcessChangeNameSwitch.visibility = View.GONE - shareProcessChangeNameContainer.visibility = View.GONE - updateViewForExternalAndLinkShare() - } + hideLinkLabelViews() + updateViewForExternalAndLinkShare() } private fun updateViewForLinkShare() { updateViewForExternalAndLinkShare() binding.run { shareProcessChangeNameSwitch.visibility = View.VISIBLE + dividerSharingChangeName.visibility = View.VISIBLE if (share != null) { - shareProcessChangeName.setText(share?.label) + shareProcessChangeNameEt.setText(share?.label) shareProcessChangeNameSwitch.isChecked = !TextUtils.isEmpty(share?.label) } - shareReadCheckbox.isEnabled = isFolder() showChangeNameInput(shareProcessChangeNameSwitch.isChecked) } + + //download limit will only be available for files + if (share?.isFolder == false || file?.isFolder == false) { + binding.shareProcessDownloadLimitSwitch.visibility = View.VISIBLE + binding.dividerSharingDownloadLimit.visibility = View.VISIBLE + + //fetch the download limit for link share + fetchDownloadLimitForShareLink() + } else { + binding.shareProcessDownloadLimitSwitch.visibility = View.GONE + binding.dividerSharingDownloadLimit.visibility = View.GONE + } + + //the input for download limit will be hidden initially + //and can be visible back or no depending on the api result + //from the download limit api + binding.shareProcessDownloadLimitEt.visibility = View.GONE + binding.shareProcessRemainingDownloadCountTv.visibility = View.GONE } private fun updateViewForInternalShare() { + hideLinkLabelViews() binding.run { - shareProcessChangeNameSwitch.visibility = View.GONE - shareProcessChangeNameContainer.visibility = View.GONE shareProcessHideDownloadCheckbox.visibility = View.GONE - shareCheckbox.setVisibleIf(!isSecureShare) + shareProcessChangeNameSwitch.visibility = View.GONE + shareProcessAllowResharingCheckbox.setVisibleIf(!isSecureShare) shareProcessSetPasswordSwitch.visibility = View.GONE if (share != null) { if (!isReShareShown) { - shareCheckbox.visibility = View.GONE + shareProcessAllowResharingCheckbox.visibility = View.GONE } - shareCheckbox.isChecked = SharePermissionManager.canReshare(share) + shareProcessAllowResharingCheckbox.isChecked = SharePermissionManager.canReshare(share) + if (share?.isFolder == true) { + hideFileDropView() + } + } else if (file?.isFolder == true) { + hideFileDropView() } } } + private fun hideFileDropView() { + //no file drop for internal share due to 403 bad request api issue + binding.fileDropRadioButton.visibility = View.GONE + binding.shareFileDropInfo.visibility = View.GONE + } + + private fun hideLinkLabelViews() { + binding.shareProcessChangeNameSwitch.visibility = View.GONE + binding.shareProcessChangeNameEt.visibility = View.GONE + binding.dividerSharingChangeName.visibility = View.GONE + + binding.shareProcessDownloadLimitSwitch.visibility = View.GONE + binding.shareProcessDownloadLimitEt.visibility = View.GONE + binding.shareProcessRemainingDownloadCountTv.visibility = View.GONE + binding.dividerSharingDownloadLimit.visibility = View.GONE + } + private fun updateViewForExternalAndLinkShare() { binding.run { shareProcessHideDownloadCheckbox.visibility = View.VISIBLE - shareCheckbox.visibility = View.GONE + dividerSharingHideDownload.visibility = View.VISIBLE + shareProcessAllowResharingCheckbox.visibility = View.GONE + shareProcessAllowResharingInfo.visibility = View.GONE + dividerSharingAllowResharing.visibility = View.GONE shareProcessSetPasswordSwitch.visibility = View.VISIBLE + dividerSharingEnterPassword.visibility = View.VISIBLE if (share != null) { if (SharePermissionManager.isFileRequest(share)) { - shareProcessHideDownloadCheckbox.visibility = View.GONE + shareProcessHideDownloadCheckbox.isChecked = true + shareProcessHideDownloadCheckbox.isEnabled = false + dividerSharingHideDownload.visibility = View.GONE } else { - shareProcessHideDownloadCheckbox.visibility = View.VISIBLE + shareProcessHideDownloadCheckbox.isEnabled = true + dividerSharingHideDownload.visibility = View.VISIBLE shareProcessHideDownloadCheckbox.isChecked = share?.isHideFileDownload == true } } @@ -490,31 +488,23 @@ class FileDetailsSharingProcessFragment : } } - private fun updateFileDownloadLimitView() { - if (canSetDownloadLimit()) { - binding.shareProcessSetDownloadLimitSwitch.visibility = View.VISIBLE - - val currentDownloadLimit = share?.fileDownloadLimit?.limit ?: capabilities.filesDownloadLimitDefault - if (currentDownloadLimit > 0) { - binding.shareProcessSetDownloadLimitSwitch.isChecked = true - showFileDownloadLimitInput(true) - binding.shareProcessSetDownloadLimitInput.setText("$currentDownloadLimit") - } - } - } - private fun updateViewForFile() { binding.run { canEditRadioButton.text = getString(R.string.link_share_editing) + fileDropRadioButton.visibility = View.GONE + shareFileDropInfo.visibility = View.GONE } } private fun updateViewForFolder() { binding.run { - canEditRadioButton.text = getString(R.string.share_permission_can_edit) - + canEditRadioButton.text = getString(R.string.link_share_allow_upload_and_editing) + fileDropRadioButton.visibility = View.VISIBLE + shareFileDropInfo.visibility = View.VISIBLE if (isSecureShare) { - shareCheckbox.visibility = View.GONE + fileDropRadioButton.visibility = View.GONE + shareFileDropInfo.visibility = View.GONE + shareProcessAllowResharingCheckbox.visibility = View.GONE shareProcessSetExpDateSwitch.visibility = View.GONE } } @@ -523,17 +513,15 @@ class FileDetailsSharingProcessFragment : private fun updateViewForNoteScreenType() { binding.run { shareProcessGroupOne.visibility = View.GONE - shareProcessEditShareLink.visibility = View.GONE shareProcessGroupTwo.visibility = View.VISIBLE if (share != null) { - shareProcessBtnNext.text = getString(R.string.set_note) + shareProcessBtnNext.text = getString(R.string.send_email) noteText.setText(share?.note) } else { shareProcessBtnNext.text = getString(R.string.send_share) noteText.setText(R.string.empty) } shareProcessStep = SCREEN_TYPE_NOTE - shareProcessBtnNext.performClick() } } // endregion @@ -557,15 +545,22 @@ class FileDetailsSharingProcessFragment : shareProcessSetExpDateSwitch.setOnCheckedChangeListener { _, isChecked -> showExpirationDateInput(isChecked) } - shareProcessSetDownloadLimitSwitch.setOnCheckedChangeListener { _, isChecked -> - showFileDownloadLimitInput(isChecked) - } shareProcessChangeNameSwitch.setOnCheckedChangeListener { _, isChecked -> showChangeNameInput(isChecked) } shareProcessSelectExpDate.setOnClickListener { showExpirationDateDialog() } + shareProcessDownloadLimitSwitch.setOnCheckedChangeListener { _, isChecked -> + showDownloadLimitInput(isChecked) + } + noteText.setOnTouchListener { view, event -> + view.parent.requestDisallowInterceptTouchEvent(true) + if ((event.action and MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP) { + view.parent.requestDisallowInterceptTouchEvent(false) + } + return@setOnTouchListener false + } // region RadioButtons shareRadioGroup.setOnCheckedChangeListener { _, optionId -> @@ -578,36 +573,52 @@ class FileDetailsSharingProcessFragment : permission = SharePermissionManager.getMaximumPermission(isFolder()) } - R.id.file_request_radio_button -> { + R.id.file_drop_radio_button -> { permission = OCShare.CREATE_PERMISSION_FLAG } } - val isCustomPermissionSelected = (optionId == R.id.custom_permission_radio_button) - customPermissionLayout.setVisibilityWithAnimation(isCustomPermissionSelected) - toggleNextButtonAvailability(true) + // NMC customization: after every permission check toggle allow reshare + togglePermission(binding.shareProcessAllowResharingCheckbox.isChecked, OCShare.SHARE_PERMISSION_FLAG) } // endregion } } - private fun isAnySharePermissionChecked(): Boolean = binding.run { - isSharePermissionChecked() || isCustomPermissionSelectedAndAnyCustomPermissionTypeChecked() - } - - private fun isSharePermissionChecked(): Boolean = binding.run { - viewOnlyRadioButton.isChecked || canEditRadioButton.isChecked || fileRequestRadioButton.isChecked + // NMC-4653 fix + // add editor action listener to clear focus + private fun implementEditorListener() { + binding.run { + shareProcessEnterPassword.setOnEditorActionListener { v, actionId, event -> + if (actionId == EditorInfo.IME_ACTION_DONE) { + shareProcessEnterPassword.clearFocus() + true + } + false + } + shareProcessChangeNameEt.setOnEditorActionListener { v, actionId, event -> + if (actionId == EditorInfo.IME_ACTION_DONE) { + shareProcessChangeNameEt.clearFocus() + true + } + false + } + shareProcessDownloadLimitEt.setOnEditorActionListener { v, actionId, event -> + if (actionId == EditorInfo.IME_ACTION_DONE) { + shareProcessDownloadLimitEt.clearFocus() + true + } + false + } + } } - private fun isCustomPermissionSelectedAndAnyCustomPermissionTypeChecked(): Boolean = binding.run { - customPermissionRadioButton.isChecked && - ( - (shareReadCheckbox.isEnabled && shareReadCheckbox.isChecked) || - (shareCreateCheckbox.isVisible && shareCreateCheckbox.isChecked) || - shareEditCheckbox.isChecked || - (shareCheckbox.isVisible && shareCheckbox.isChecked) || - (shareDeleteCheckbox.isEnabled && shareDeleteCheckbox.isChecked) - ) + private fun isAnySharePermissionChecked(): Boolean { + return binding.run { + viewOnlyRadioButton.isChecked || + canEditRadioButton.isChecked || + fileDropRadioButton.isChecked + } || permission != OCShare.NO_PERMISSION } private fun toggleNextButtonAvailability(value: Boolean) { @@ -623,25 +634,10 @@ class FileDetailsSharingProcessFragment : binding.run { SharePermissionManager.run { - shareReadCheckbox.isChecked = hasPermission(currentPermissions, OCShare.READ_PERMISSION_FLAG) - shareEditCheckbox.isChecked = hasPermission(currentPermissions, OCShare.UPDATE_PERMISSION_FLAG) - shareCheckbox.isChecked = hasPermission(currentPermissions, OCShare.SHARE_PERMISSION_FLAG) - - if (isFolder()) { - // Only for the folder makes sense to have create permission - // so that user can create files in the shared folder - shareCreateCheckbox.isChecked = hasPermission(currentPermissions, OCShare.CREATE_PERMISSION_FLAG) - shareDeleteCheckbox.isChecked = hasPermission(currentPermissions, OCShare.DELETE_PERMISSION_FLAG) - } else { - shareCreateCheckbox.visibility = View.GONE - shareDeleteCheckbox.apply { - isChecked = false - isEnabled = false - } - } + shareProcessAllowResharingCheckbox.isChecked = hasPermission(currentPermissions, OCShare.SHARE_PERMISSION_FLAG) if (!isPublicShare()) { - shareAllowDownloadAndSyncCheckbox.isChecked = + shareProcessHideDownloadCheckbox.isChecked = share?.isAllowDownloadAndSyncEnabled(useV2DownloadAttributes()) == true } } @@ -652,11 +648,7 @@ class FileDetailsSharingProcessFragment : private fun setCheckboxesListeners() { val checkboxes = mapOf( - binding.shareReadCheckbox to OCShare.READ_PERMISSION_FLAG, - binding.shareCreateCheckbox to OCShare.CREATE_PERMISSION_FLAG, - binding.shareEditCheckbox to OCShare.UPDATE_PERMISSION_FLAG, - binding.shareCheckbox to OCShare.SHARE_PERMISSION_FLAG, - binding.shareDeleteCheckbox to OCShare.DELETE_PERMISSION_FLAG + binding.shareProcessAllowResharingCheckbox to OCShare.SHARE_PERMISSION_FLAG, ) checkboxes.forEach { (checkbox, flag) -> @@ -664,7 +656,7 @@ class FileDetailsSharingProcessFragment : } if (!isPublicShare()) { - binding.shareAllowDownloadAndSyncCheckbox.setOnCheckedChangeListener { _, isChecked -> + binding.shareProcessHideDownloadCheckbox.setOnCheckedChangeListener { _, isChecked -> val result = toggleAllowDownloadAndSync(share?.attributes, isChecked, useV2DownloadAttributes()) share?.attributes = result downloadAttribute = result @@ -690,14 +682,31 @@ class FileDetailsSharingProcessFragment : } private fun showChangeNameInput(isChecked: Boolean) { - binding.shareProcessChangeNameContainer.setVisibleIf(isChecked) + binding.shareProcessChangeNameEt.setVisibleIf(isChecked) if (!isChecked) { - binding.shareProcessChangeName.setText(R.string.empty) + binding.shareProcessChangeNameEt.setText(R.string.empty) + // hide keyboard when user unchecks + hideKeyboard() + } + } + + private fun showDownloadLimitInput(isChecked: Boolean) { + binding.shareProcessDownloadLimitEt.visibility = if (isChecked) View.VISIBLE else View.GONE + binding.shareProcessRemainingDownloadCountTv.visibility = if (isChecked) View.VISIBLE else View.GONE + if (!isChecked) { + binding.shareProcessDownloadLimitEt.setText(R.string.empty) + if (!isDownloadCountFetched) { + binding.shareProcessRemainingDownloadCountTv.text = String.format(resources.getString(R.string.download_text), "0") + } + //hide keyboard when user unchecks + hideKeyboard() } } private fun onCancelClick() { + // hide keyboard when user clicks cancel button + hideKeyboard() // if modifying the existing share then on back press remove the current fragment if (share != null) { removeCurrentFragment() @@ -718,6 +727,13 @@ class FileDetailsSharingProcessFragment : binding.shareProcessSelectExpDate.setVisibleIf(isChecked) binding.shareProcessExpDateDivider.setVisibleIf(isChecked) + //update margin of divider when switch is enabled/disabled + val margin = if (isChecked) requireContext().resources.getDimensionPixelOffset(R.dimen.standard_half_margin) + else 0 + val param = binding.dividerSharingExpDate.layoutParams as ViewGroup.MarginLayoutParams + param.setMargins(0, margin, 0, 0) + binding.dividerSharingExpDate.layoutParams = param + // reset the expiration date if switch is unchecked if (!isChecked) { chosenExpDateInMills = -1 @@ -725,27 +741,29 @@ class FileDetailsSharingProcessFragment : } } - private fun showFileDownloadLimitInput(isChecked: Boolean) { - binding.shareProcessSetDownloadLimitInputContainer.setVisibleIf(isChecked) - - // reset download limit if switch is unchecked - if (!isChecked) { - binding.shareProcessSetDownloadLimitInput.setText(R.string.empty) - } - } - private fun showPasswordInput(isChecked: Boolean) { - binding.shareProcessEnterPasswordContainer.setVisibleIf(isChecked) + binding.shareProcessEnterPassword.setVisibleIf(isChecked) // reset the password if switch is unchecked if (!isChecked) { binding.shareProcessEnterPassword.setText(R.string.empty) + // hide keyboard when user unchecks + hideKeyboard() + } + } + + private fun hideKeyboard() { + if (this::binding.isInitialized) { + keyboardUtils.hideKeyboardFrom(requireContext(), binding.root) } } + /** + * remove the fragment and pop it from backstack because we are adding it to backstack + */ private fun removeCurrentFragment() { - onEditShareListener.onShareProcessClosed() - fileActivity?.supportFragmentManager?.beginTransaction()?.remove(this)?.commit() + requireActivity().supportFragmentManager.beginTransaction().remove(this).commit() + requireActivity().supportFragmentManager.popBackStack() } /** @@ -753,6 +771,7 @@ class FileDetailsSharingProcessFragment : */ @Suppress("ReturnCount") private fun validateShareProcessFirst() { + hideKeyboard() if (permission == OCShare.NO_PERMISSION) { DisplayUtils.showSnackMessage(binding.root, R.string.no_share_permission_selected) return @@ -773,18 +792,21 @@ class FileDetailsSharingProcessFragment : } if (binding.shareProcessChangeNameSwitch.isChecked && - binding.shareProcessChangeName.text?.isBlank() == true + binding.shareProcessChangeNameEt.text?.isBlank() == true ) { DisplayUtils.showSnackMessage(binding.root, R.string.label_empty) return } - if (!isSharePermissionChecked() && !isCustomPermissionSelectedAndAnyCustomPermissionTypeChecked()) { - DisplayUtils.showSnackMessage( - binding.root, - R.string.file_details_sharing_fragment_custom_permission_not_selected - ) - return + if (binding.shareProcessDownloadLimitSwitch.isChecked) { + val downloadLimit = binding.shareProcessDownloadLimitEt.text?.trim() + if (downloadLimit.isNullOrEmpty()) { + DisplayUtils.showSnackMessage(binding.root, R.string.download_limit_empty) + return + } else if (downloadLimit.toString().toLong() <= 0) { + DisplayUtils.showSnackMessage(binding.root, R.string.download_limit_zero) + return + } } // if modifying existing share information then execute the process @@ -835,38 +857,31 @@ class FileDetailsSharingProcessFragment : share?.attributes = null } + var newDownloadLimit = binding.shareProcessDownloadLimitEt.text.toString().trim() + // when current and previous limit is same pass -1 to skip updating download limit + // NMC-3928 fix + // updating download limit resets the download counter + if (downloadLimit != null && newDownloadLimit == downloadLimit) { + newDownloadLimit = "-1" + } + fileOperationsHelper?.updateShareInformation( share, permission, binding.shareProcessHideDownloadCheckbox.isChecked, binding.shareProcessEnterPassword.text.toString().trim(), chosenExpDateInMills, - binding.shareProcessChangeName.text.toString().trim() - ) - - if (canSetDownloadLimit()) { - setDownloadLimit() - } - - // copy the share link if available - if (!TextUtils.isEmpty(share?.shareLink)) { - ClipboardUtil.copyToClipboard(requireActivity(), share?.shareLink) - } - } - - private fun setDownloadLimit() { - val downloadLimitInput = binding.shareProcessSetDownloadLimitInput.text.toString().trim() - val downloadLimit = - if (binding.shareProcessSetDownloadLimitSwitch.isChecked && downloadLimitInput.isNotEmpty()) { - downloadLimitInput.toInt() - } else { - 0 + binding.shareProcessChangeNameEt.text.toString().trim(), + newDownloadLimit + ) + // copy the share link if available + if (!TextUtils.isEmpty(share?.shareLink)) { + ClipboardUtil.copyToClipboard(requireActivity(), share?.shareLink) } - - fileOperationsHelper?.updateFilesDownloadLimit(share, downloadLimit) - } + } private fun createShare(noteText: String) { + hideKeyboard() fileOperationsHelper?.shareFileWithSharee( file, shareeName, @@ -877,11 +892,24 @@ class FileDetailsSharingProcessFragment : chosenExpDateInMills, noteText, downloadAttribute, - binding.shareProcessChangeName.text.toString().trim(), + binding.shareProcessChangeNameEt.text.toString().trim(), true ) } + /** + * fetch the download limit for the link share + * the response will be received in FileActivity --> onRemoteOperationFinish() method + */ + private fun fetchDownloadLimitForShareLink() { + //need to call this method in handler else to show progress dialog it will throw exception + Handler(Looper.getMainLooper()).post { + share?.let { + fileOperationsHelper?.getShareDownloadLimit(it.token) + } + } + } + /** * method will be called from DrawerActivity on back press to handle screen backstack */ @@ -901,14 +929,21 @@ class FileDetailsSharingProcessFragment : binding.shareProcessSetExpDateSwitch.isChecked = false } + /** + * will be called when download limit is fetched + */ + fun onLinkShareDownloadLimitFetched(downloadLimit: Long, downloadCount: Long) { + binding.shareProcessDownloadLimitSwitch.isChecked = downloadLimit > 0 + showDownloadLimitInput(binding.shareProcessDownloadLimitSwitch.isChecked) + binding.shareProcessDownloadLimitEt.setText(if (downloadLimit > 0) downloadLimit.toString() else "") + binding.shareProcessRemainingDownloadCountTv.text = String.format(resources.getString(R.string.download_text), downloadCount.toString()) + isDownloadCountFetched = true + this.downloadLimit = downloadLimit.toString() + } + // region Helpers private fun isShareProcessStepIsPermission(): Boolean = ( - shareProcessStep == SCREEN_TYPE_PERMISSION || - isShareProcessStepIsCustomPermission() - ) - - private fun isShareProcessStepIsCustomPermission(): Boolean = - (shareProcessStep == SCREEN_TYPE_PERMISSION_WITH_CUSTOM_PERMISSION) + shareProcessStep == SCREEN_TYPE_PERMISSION) private fun isShareProcessStepIsNote(): Boolean = (shareProcessStep == SCREEN_TYPE_NOTE) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java index 981555d062b7..bd51a1febcd6 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -95,6 +95,7 @@ import com.owncloud.android.ui.dialog.RenameFileDialogFragment; import com.owncloud.android.ui.dialog.SyncFileNotEnoughSpaceDialogFragment; import com.owncloud.android.ui.dialog.setupEncryption.SetupEncryptionDialogFragment; +import com.owncloud.android.ui.dialog.SendShareDialog; import com.owncloud.android.ui.events.ChangeMenuEvent; import com.owncloud.android.ui.events.CommentsEvent; import com.owncloud.android.ui.events.EncryptionEvent; @@ -642,6 +643,8 @@ public void createRichWorkspace() { @Override public void onShareIconClick(OCFile file) { + //NMC Customization + SendShareDialog.isPeopleShareClicked = true; if (file.isFolder()) { mContainerActivity.showDetails(file, 1); } else { @@ -1353,6 +1356,8 @@ public boolean onFileActionChosen(@IdRes final int itemId, Set checkedFi OCFile singleFile = checkedFiles.iterator().next(); if (itemId == R.id.action_send_share_file) { + //NMC Customization + SendShareDialog.isPeopleShareClicked = true; mContainerActivity.getFileOperationsHelper().sendShareFile(singleFile); return true; } else if (itemId == R.id.action_open_file_with) { @@ -1381,7 +1386,8 @@ public boolean onFileActionChosen(@IdRes final int itemId, Set checkedFi if (mActiveActionMode != null) { mActiveActionMode.finish(); } - + //NMC Customization + SendShareDialog.isPeopleShareClicked = true; mContainerActivity.showDetails(singleFile); mContainerActivity.showSortListGroup(false); return true; diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/QuickSharingPermissionsBottomSheetDialog.java b/app/src/main/java/com/owncloud/android/ui/fragment/QuickSharingPermissionsBottomSheetDialog.java index 75ae214383f3..a7f60f882b4a 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/QuickSharingPermissionsBottomSheetDialog.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/QuickSharingPermissionsBottomSheetDialog.java @@ -21,7 +21,6 @@ import com.owncloud.android.R; import com.owncloud.android.databinding.QuickSharingPermissionsBottomSheetFragmentBinding; import com.owncloud.android.datamodel.quickPermission.QuickPermission; -import com.owncloud.android.datamodel.quickPermission.QuickPermissionType; import com.owncloud.android.lib.resources.shares.OCShare; import com.owncloud.android.ui.activity.FileActivity; import com.owncloud.android.ui.adapter.QuickSharingPermissionsAdapter; @@ -33,9 +32,8 @@ 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; +import static com.owncloud.android.lib.resources.shares.OCShare.SHARE_PERMISSION_FLAG; /** * File Details Quick Sharing permissions options {@link Dialog} styled as a bottom sheet for main actions. @@ -71,8 +69,6 @@ protected void onCreate(Bundle savedInstanceState) { getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); } - viewThemeUtils.platform.themeDialog(binding.getRoot()); - setUpRecyclerView(); setOnShowListener(d -> BottomSheetBehavior.from((View) binding.getRoot().getParent()) @@ -87,8 +83,8 @@ private void setUpRecyclerView() { new QuickSharingPermissionsAdapter.QuickSharingPermissionViewHolder.OnPermissionChangeListener() { @Override public void onCustomPermissionSelected() { + // NMC Customizations: No action will be required dismiss(); - actions.openShareDetailWithCustomPermissions(ocShare); } @Override @@ -112,8 +108,24 @@ public void onDismissSheet() { * Handle permission changed on click of selected permission */ private void handlePermissionChanged(List quickPermissionList, int position) { - final var type = quickPermissionList.get(position).getType(); - int permissionFlag = type.getPermissionFlag(ocShare.isFolder()); + final var permissionName = quickPermissionList.get(position).getType().getText(getContext()); + final var res = fileActivity.getResources(); + + int permissionFlag = 0; + if (permissionName.equalsIgnoreCase(res.getString(R.string.share_permission_can_edit)) || permissionName.equalsIgnoreCase(res.getString(R.string.link_share_editing))) { + permissionFlag = SharePermissionManager.INSTANCE.getMaximumPermission(ocShare.isFolder()); + } else if (permissionName.equalsIgnoreCase(res.getString(R.string.share_permission_read_only))) { + permissionFlag = READ_PERMISSION_FLAG; + } else if (permissionName.equalsIgnoreCase(res.getString(R.string.share_permission_file_drop))) { + permissionFlag = CREATE_PERMISSION_FLAG; + } + + // NMC Customization: after permission change check if share already has reshare allowed + // if allowed then toggle permission flag + if (SharePermissionManager.INSTANCE.canReshare(ocShare)) { + permissionFlag = SharePermissionManager.INSTANCE.togglePermission(true, permissionFlag, SHARE_PERMISSION_FLAG); + } + actions.onQuickPermissionChanged(ocShare, permissionFlag); dismiss(); } @@ -135,7 +147,5 @@ protected void onStop() { public interface QuickPermissionSharingBottomSheetActions { void onQuickPermissionChanged(OCShare share, int permission); - - void openShareDetailWithCustomPermissions(OCShare share); } } diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/util/SharePermissionManager.kt b/app/src/main/java/com/owncloud/android/ui/fragment/util/SharePermissionManager.kt index 313cbd073d45..7de5772200e5 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/util/SharePermissionManager.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/util/SharePermissionManager.kt @@ -7,13 +7,27 @@ package com.owncloud.android.ui.fragment.util +import android.content.Context +import com.nextcloud.client.account.User +import com.nextcloud.utils.EditorUtils +import com.owncloud.android.R +import com.owncloud.android.datamodel.OCFile import com.owncloud.android.datamodel.quickPermission.QuickPermissionType import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.lib.resources.shares.OCShare +import com.owncloud.android.lib.resources.status.OCCapability import com.owncloud.android.ui.fragment.FileDetailsSharingProcessFragment.Companion.TAG object SharePermissionManager { + // NMC Customization + // updated Edit permissions for folder and files + // because the MAXIMUM_PERMISSIONS_FOR_FILE and MAXIMUM_PERMISSIONS_FOR_FOLDER permission in OCShare + // are not valid due to functionality changes + const val CAN_EDIT_PERMISSIONS_FOR_FILE = OCShare.READ_PERMISSION_FLAG + OCShare.UPDATE_PERMISSION_FLAG + const val CAN_EDIT_PERMISSIONS_FOR_FOLDER = + CAN_EDIT_PERMISSIONS_FOR_FILE + OCShare.CREATE_PERMISSION_FLAG + OCShare.DELETE_PERMISSION_FLAG + // region Permission change fun togglePermission(isChecked: Boolean, permission: Int, permissionFlag: Int): Int { Log_OC.d(TAG, "togglePermission before: $permission") @@ -76,11 +90,8 @@ object SharePermissionManager { return false } - return share.permissions != OCShare.NO_PERMISSION && - ( - share.permissions == OCShare.READ_PERMISSION_FLAG || - share.permissions == OCShare.READ_PERMISSION_FLAG + OCShare.SHARE_PERMISSION_FLAG - ) + // NMC Customization: add check for Allow Reshare permission + return share.permissions != OCShare.NO_PERMISSION && ((share.permissions and OCShare.Companion.SHARE_PERMISSION_FLAG.inv()) == OCShare.READ_PERMISSION_FLAG) } @Suppress("ReturnCount") @@ -93,7 +104,8 @@ object SharePermissionManager { return false } - return share.permissions != OCShare.NO_PERMISSION && share.permissions == OCShare.CREATE_PERMISSION_FLAG + // NMC Customization: add check for Allow Reshare permission + return share.permissions != OCShare.NO_PERMISSION && ((share.permissions and OCShare.Companion.SHARE_PERMISSION_FLAG.inv()) == OCShare.CREATE_PERMISSION_FLAG) } fun isSecureFileDrop(share: OCShare?): Boolean { @@ -153,10 +165,75 @@ object SharePermissionManager { } } + // NMC Customization: for NMC no full permission is required + // we use our custom permission fun getMaximumPermission(isFolder: Boolean): Int = if (isFolder) { - OCShare.MAXIMUM_PERMISSIONS_FOR_FOLDER + CAN_EDIT_PERMISSIONS_FOR_FOLDER } else { - OCShare.MAXIMUM_PERMISSIONS_FOR_FILE + CAN_EDIT_PERMISSIONS_FOR_FILE } // endregion + + // NMC method + /** + * method to check if the file should not be a text file or any of the office files + * this method will be used during sharing process to disable/enable edit option + */ + @JvmStatic + fun canEditFile( + user: User, + capability: OCCapability, file: OCFile, + editorUtils: EditorUtils + ): Boolean { + //if OCFile is folder then no need to check further direct return true + //as edit permission should be available for folder restriction is only applicable for files + + if (file.isFolder) { + return true + } + + //check for text files like .md, .txt, etc + val isTextFile = editorUtils.isEditorAvailable(user, file.mimeType) && !file.isEncrypted + + //check for office files like .docx, .pptx, .xls, etc + val isOfficeFile = + capability.richDocumentsMimeTypeList != null && capability.richDocumentsMimeTypeList!!.contains(file.mimeType) && + capability.richDocumentsDirectEditing.isTrue && !file.isEncrypted + + return isTextFile || isOfficeFile + } + + // function to provide quick permission label for NMC + @JvmStatic + fun getPermissionName(context: Context, share: OCShare?): String? { + if (canEdit(share)) { + return context.resources.getString(R.string.share_quick_permission_can_edit) + } else if (isViewOnly(share)) { + return context.resources.getString(R.string.share_quick_permission_can_view) + } else if (isFileRequest(share)) { + return context.resources.getString(R.string.share_quick_permission_can_upload) + } + return null + } + + // function to provide quick permission short label for NMC + // will be used in small screen devices where text overlaps + @JvmStatic + fun getShortPermissionName(context: Context, permissionName: String?): String? { + return when (permissionName) { + context.resources.getString(R.string.share_quick_permission_can_edit) -> { + context.resources.getString(R.string.share_quick_permission_can_edit_short) + } + + context.resources.getString(R.string.share_quick_permission_can_view) -> { + context.resources.getString(R.string.share_quick_permission_can_view_short) + } + + context.resources.getString(R.string.share_quick_permission_can_upload) -> { + context.resources.getString(R.string.share_quick_permission_can_upload_short) + } + + else -> permissionName + } + } } diff --git a/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java b/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java index 283e191e60c5..58c30f3f2c2d 100755 --- a/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java +++ b/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java @@ -602,9 +602,10 @@ public void unShareShare(ServerFileInterface file, long shareId) { private void queueShareIntent(Intent shareIntent) { // Unshare the file - mWaitingForOpId = fileActivity.getOperationsServiceBinder().queueNewOperation(shareIntent); - - fileActivity.showLoadingDialog(fileActivity.getApplicationContext().getString(R.string.wait_a_moment)); + if(fileActivity.getOperationsServiceBinder() != null) { + mWaitingForOpId = fileActivity.getOperationsServiceBinder().queueNewOperation(shareIntent); + fileActivity.showLoadingDialog(fileActivity.getApplicationContext().getString(R.string.wait_a_moment)); + } } /** @@ -756,7 +757,8 @@ public void updateShareInformation(OCShare share, boolean hideFileDownload, String password, long expirationTimeInMillis, - String label) { + String label, + String downloadLimit) { final var id = share.getId(); final var attributes = share.getAttributes(); @@ -779,6 +781,26 @@ public void updateShareInformation(OCShare share, updateShareIntent.putExtra(OperationsService.EXTRA_SHARE_EXPIRATION_DATE_IN_MILLIS, expirationTimeInMillis); updateShareIntent.putExtra(OperationsService.EXTRA_SHARE_PUBLIC_LABEL, (label == null) ? "" : label); updateShareIntent.putExtra(OperationsService.EXTRA_SHARE_ATTRIBUTES, attributes); + + //download limit for link share type + updateShareIntent.putExtra(OperationsService.EXTRA_SHARE_DOWNLOAD_LIMIT, + (TextUtils.isEmpty(downloadLimit)) ? 0 : + Long.parseLong(downloadLimit)); + + queueShareIntent(updateShareIntent); + } + + /** + * method to fetch the download limit for the particular share Note: Download limit is only for Link share type + * + * @param shareToken of the OCShare + */ + public void getShareDownloadLimit(String shareToken) { + Intent updateShareIntent = new Intent(fileActivity, OperationsService.class); + updateShareIntent.setAction(OperationsService.ACTION_GET_SHARE_DOWNLOAD_LIMIT); + updateShareIntent.putExtra(OperationsService.EXTRA_ACCOUNT, fileActivity.getAccount()); + updateShareIntent.putExtra(OperationsService.EXTRA_SHARE_TOKEN, shareToken); + queueShareIntent(updateShareIntent); } diff --git a/app/src/main/java/com/owncloud/android/utils/KeyboardUtils.kt b/app/src/main/java/com/owncloud/android/utils/KeyboardUtils.kt index 08bde79d95ad..74b7548131b7 100644 --- a/app/src/main/java/com/owncloud/android/utils/KeyboardUtils.kt +++ b/app/src/main/java/com/owncloud/android/utils/KeyboardUtils.kt @@ -9,6 +9,10 @@ package com.owncloud.android.utils import android.view.Window +import android.app.Activity +import android.content.Context +import android.view.View +import android.view.inputmethod.InputMethodManager import android.widget.EditText import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat @@ -22,4 +26,10 @@ class KeyboardUtils @Inject constructor() { WindowCompat.getInsetsController(window, editText).show(WindowInsetsCompat.Type.ime()) } } + + fun hideKeyboardFrom(context: Context, view: View) { + view.clearFocus() + val imm = context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(view.windowToken, 0) + } } diff --git a/app/src/main/res/color/share_contact_icon_color.xml b/app/src/main/res/color/share_contact_icon_color.xml new file mode 100644 index 000000000000..bc179324d20c --- /dev/null +++ b/app/src/main/res/color/share_contact_icon_color.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-night/ic_internal_share.xml b/app/src/main/res/drawable-night/ic_internal_share.xml new file mode 100644 index 000000000000..1a3f4dbf20f0 --- /dev/null +++ b/app/src/main/res/drawable-night/ic_internal_share.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/ic_calendar.xml b/app/src/main/res/drawable/ic_calendar.xml new file mode 100644 index 000000000000..708aae2ccb04 --- /dev/null +++ b/app/src/main/res/drawable/ic_calendar.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/ic_clipboard.xml b/app/src/main/res/drawable/ic_clipboard.xml new file mode 100644 index 000000000000..9827d48041c1 --- /dev/null +++ b/app/src/main/res/drawable/ic_clipboard.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/ic_contact_book.xml b/app/src/main/res/drawable/ic_contact_book.xml index f6af68e0864a..0ecdba004e8b 100644 --- a/app/src/main/res/drawable/ic_contact_book.xml +++ b/app/src/main/res/drawable/ic_contact_book.xml @@ -7,10 +7,12 @@ - + android:viewportWidth="24" + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_external_share.xml b/app/src/main/res/drawable/ic_external_share.xml new file mode 100644 index 000000000000..8c4f995ba247 --- /dev/null +++ b/app/src/main/res/drawable/ic_external_share.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/ic_internal_share.xml b/app/src/main/res/drawable/ic_internal_share.xml new file mode 100644 index 000000000000..e6b2da36f6d0 --- /dev/null +++ b/app/src/main/res/drawable/ic_internal_share.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/ic_open_in.xml b/app/src/main/res/drawable/ic_open_in.xml new file mode 100644 index 000000000000..de3b7b7e2cea --- /dev/null +++ b/app/src/main/res/drawable/ic_open_in.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/ic_pencil_edit.xml b/app/src/main/res/drawable/ic_pencil_edit.xml new file mode 100644 index 000000000000..a1089345a7b3 --- /dev/null +++ b/app/src/main/res/drawable/ic_pencil_edit.xml @@ -0,0 +1,12 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_shared.xml b/app/src/main/res/drawable/ic_shared.xml new file mode 100644 index 000000000000..a7aa8ad46e52 --- /dev/null +++ b/app/src/main/res/drawable/ic_shared.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/ic_shared_with_me.xml b/app/src/main/res/drawable/ic_shared_with_me.xml new file mode 100644 index 000000000000..24e5a42e0c32 --- /dev/null +++ b/app/src/main/res/drawable/ic_shared_with_me.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/drawable/ic_sharing_edit.xml b/app/src/main/res/drawable/ic_sharing_edit.xml new file mode 100644 index 000000000000..b4e40bc75bea --- /dev/null +++ b/app/src/main/res/drawable/ic_sharing_edit.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_sharing_file_drop.xml b/app/src/main/res/drawable/ic_sharing_file_drop.xml new file mode 100644 index 000000000000..0dea8a079dad --- /dev/null +++ b/app/src/main/res/drawable/ic_sharing_file_drop.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_sharing_quick_permission_arrow.xml b/app/src/main/res/drawable/ic_sharing_quick_permission_arrow.xml new file mode 100644 index 000000000000..44e00e17aabd --- /dev/null +++ b/app/src/main/res/drawable/ic_sharing_quick_permission_arrow.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_sharing_read_only.xml b/app/src/main/res/drawable/ic_sharing_read_only.xml new file mode 100644 index 000000000000..cfef11314c6d --- /dev/null +++ b/app/src/main/res/drawable/ic_sharing_read_only.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/app/src/main/res/drawable/share_et_bg.xml b/app/src/main/res/drawable/share_et_bg.xml new file mode 100644 index 000000000000..10e93e811aae --- /dev/null +++ b/app/src/main/res/drawable/share_et_bg.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/share_search_background.xml b/app/src/main/res/drawable/share_search_background.xml new file mode 100644 index 000000000000..bd0eb50d4e67 --- /dev/null +++ b/app/src/main/res/drawable/share_search_background.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/sharing_email_warning_bg.xml b/app/src/main/res/drawable/sharing_email_warning_bg.xml new file mode 100644 index 000000000000..b4c4f72141cd --- /dev/null +++ b/app/src/main/res/drawable/sharing_email_warning_bg.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/layout/file_details_fragment.xml b/app/src/main/res/layout/file_details_fragment.xml index 8ed20b18a048..c27506211204 100644 --- a/app/src/main/res/layout/file_details_fragment.xml +++ b/app/src/main/res/layout/file_details_fragment.xml @@ -18,6 +18,7 @@ android:orientation="vertical"> @@ -35,8 +36,8 @@ android:layout_height="wrap_content" android:ellipsize="middle" android:text="@string/placeholder_filename" - android:textColor="@color/text_color" - android:textSize="20sp" + android:textColor="@color/share_title_txt_color" + android:textSize="@dimen/txt_size_14sp" android:textStyle="bold" tools:text="@string/placeholder_filename" /> @@ -49,8 +50,8 @@ + android:textColor="@color/share_subtitle_txt_color" + android:textSize="@dimen/two_line_secondary_text_size" /> + app:iconTint="@color/grey_60" /> @@ -174,7 +176,8 @@ android:id="@+id/syncBlock" android:layout_width="match_parent" android:orientation="vertical" - android:layout_height="wrap_content"> + android:layout_height="wrap_content" + android:visibility="gone"> - - - - + android:background="@color/divider_color" /> diff --git a/app/src/main/res/layout/file_details_share_link_share_item.xml b/app/src/main/res/layout/file_details_share_link_share_item.xml index ea32fd599b39..fb5ccf039e7f 100644 --- a/app/src/main/res/layout/file_details_share_link_share_item.xml +++ b/app/src/main/res/layout/file_details_share_link_share_item.xml @@ -9,92 +9,100 @@ ~ SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only --> - - + android:layout_marginTop="@dimen/standard_quarter_margin" + android:orientation="vertical"> + android:layout_width="match_parent" + android:layout_height="@dimen/sharee_list_item_size" + android:orientation="horizontal"> - + android:layout_marginStart="@dimen/standard_margin" + android:layout_marginTop="7dp" + android:layout_marginEnd="@dimen/standard_margin" + android:contentDescription="@string/share" + android:src="@drawable/shared_via_link" + android:visibility="gone" + app:tint="@color/text_color" /> - + + + + + + + + + android:textSize="@dimen/txt_size_15sp" /> - - + android:layout_height="match_parent" + android:contentDescription="@string/copy_link" + android:paddingStart="@dimen/standard_padding" + android:paddingEnd="@dimen/standard_padding" + android:scaleType="fitCenter" + android:src="@drawable/ic_share" + app:tint="@color/primary" /> - + - + + diff --git a/app/src/main/res/layout/file_details_share_public_link_add_new_item.xml b/app/src/main/res/layout/file_details_share_public_link_add_new_item.xml index 466cdcddc3d3..49b78772f9fb 100644 --- a/app/src/main/res/layout/file_details_share_public_link_add_new_item.xml +++ b/app/src/main/res/layout/file_details_share_public_link_add_new_item.xml @@ -14,8 +14,8 @@ android:orientation="horizontal"> - - + android:layout_width="match_parent" + android:layout_height="@dimen/sharee_list_item_size" + android:orientation="horizontal" + android:weightSum="1"> - + android:layout_marginStart="@dimen/standard_margin" + android:layout_marginLeft="@dimen/standard_margin" + android:layout_marginTop="@dimen/alternate_half_margin" + android:layout_marginEnd="@dimen/standard_margin" + android:layout_marginRight="@dimen/standard_margin" + android:contentDescription="@string/user_icon" + android:src="@drawable/ic_internal_share" + android:visibility="gone" + app:tint="@color/text_color" /> - + android:layout_gravity="center_vertical" + android:layout_marginStart="@dimen/standard_margin" + android:layout_weight="1" + android:orientation="vertical"> + + + + + + + - + diff --git a/app/src/main/res/layout/file_details_sharing_fragment.xml b/app/src/main/res/layout/file_details_sharing_fragment.xml index 310c49197c6e..62d7ed104558 100644 --- a/app/src/main/res/layout/file_details_sharing_fragment.xml +++ b/app/src/main/res/layout/file_details_sharing_fragment.xml @@ -8,9 +8,9 @@ + android:layout_height="match_parent"> - - - - - - - - - - - - - - - - - + android:paddingRight="@dimen/standard_padding" + android:text="@string/sharing_heading" + android:textColor="@color/share_title_txt_color" + android:textSize="@dimen/txt_size_16sp" + android:textStyle="bold" /> + + + + - - + android:layout_marginTop="@dimen/standard_margin" + android:layout_marginBottom="@dimen/standard_margin" + android:gravity="center_vertical" + android:orientation="horizontal"> + android:layout_width="@dimen/share_search_height" + android:layout_height="@dimen/share_search_height" + android:layout_marginEnd="@dimen/standard_margin" + android:background="@drawable/share_search_background" + app:srcCompat="@drawable/ic_contact_book" + app:tint="@color/share_contact_icon_color" /> + + + + + + + + + - + android:layout_marginStart="@dimen/standard_margin" + android:layout_marginTop="@dimen/standard_quarter_margin" + android:layout_marginEnd="@dimen/standard_margin" + android:text="@string/copy_link" + android:textColor="@color/text_color" + android:textSize="@dimen/txt_size_17sp" + android:textStyle="bold" /> - - + android:dividerHeight="1dp" /> + android:layout_height="@dimen/create_link_button_height" + android:layout_margin="@dimen/standard_margin" + android:text="@string/create_link" + android:textColor="@color/text_color" + android:textStyle="normal" + android:typeface="normal" /> - - + android:layout_height="0.5dp" + android:layout_marginTop="@dimen/standard_margin" + android:layout_marginBottom="@dimen/standard_margin" + android:background="@color/divider_color" /> - + android:layout_marginStart="@dimen/standard_margin" + android:layout_marginTop="@dimen/standard_quarter_margin" + android:layout_marginEnd="@dimen/standard_margin" + android:text="@string/shared_with_heading" + android:textColor="@color/text_color" + android:textSize="@dimen/txt_size_17sp" + android:textStyle="bold" /> + android:dividerHeight="1dp" /> - + - + android:layout_height="80dp" /> + + android:paddingTop="@dimen/standard_padding"> + + + + + + + + @@ -52,7 +85,8 @@ @@ -84,7 +118,8 @@ @@ -118,7 +153,8 @@ diff --git a/app/src/main/res/layout/file_details_sharing_process_fragment.xml b/app/src/main/res/layout/file_details_sharing_process_fragment.xml index 60ac7edbebd0..1e9ebabd654a 100644 --- a/app/src/main/res/layout/file_details_sharing_process_fragment.xml +++ b/app/src/main/res/layout/file_details_sharing_process_fragment.xml @@ -6,181 +6,278 @@ ~ SPDX-FileCopyrightText: 2021 Nextcloud GmbH ~ SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only --> - - + android:layout_height="wrap_content" + android:minHeight="400dp"> + + + android:textColor="@color/share_title_txt_color" + android:textSize="@dimen/txt_size_16sp" + android:textStyle="bold" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/tv_sharing_title" /> + + + android:layout_gravity="end" + android:orientation="vertical" + android:paddingLeft="@dimen/standard_padding" + android:paddingRight="@dimen/standard_padding" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/divider_sharing_permissions"> + android:background="?android:selectableItemBackground" + android:button="@null" + android:drawableEnd="?android:attr/listChoiceIndicatorSingle" + android:text="@string/link_share_read_only" + android:textColor="@color/share_txt_color" + android:textSize="@dimen/txt_size_15sp" /> + android:background="?android:selectableItemBackground" + android:button="@null" + android:drawableEnd="?android:attr/listChoiceIndicatorSingle" + android:text="@string/link_share_allow_upload_and_editing" + android:textColor="@color/share_txt_color" + android:textSize="@dimen/txt_size_15sp" /> - - + android:background="?android:selectableItemBackground" + android:button="@null" + android:drawableEnd="?android:attr/listChoiceIndicatorSingle" + android:text="@string/link_share_file_drop" + android:textColor="@color/share_txt_color" + android:textSize="@dimen/txt_size_15sp" /> - + + + + + + - - - + android:layout_height="1dp" + android:background="@color/list_divider_background" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/share_process_hide_download_checkbox" /> - - - - - - - + - + - + - - + + - - - - + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/share_process_set_password_switch" + tools:visibility="visible" /> - + + android:layout_marginLeft="@dimen/standard_margin" + android:layout_marginRight="@dimen/standard_margin" + android:text="@string/share_no_expiration_date_label" + android:textColor="@color/share_txt_color" + android:textSize="@dimen/txt_size_15sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/divider_sharing_enter_password" /> + android:layout_marginLeft="@dimen/share_exp_date_divider_margin" + android:layout_marginRight="@dimen/share_exp_date_divider_margin" + android:background="@color/share_et_divider" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/share_process_select_exp_date" /> + + + android:layout_marginLeft="@dimen/standard_margin" + android:layout_marginRight="@dimen/standard_margin" + android:text="@string/link_label" + android:textColor="@color/share_txt_color" + android:textSize="@dimen/txt_size_15sp" + android:visibility="gone" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/divider_sharing_exp_date" + tools:visibility="visible" /> - - - - - + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/share_process_change_name_switch" + tools:visibility="visible" /> - + + + + - - - - - - - + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/share_process_download_limit_et" + tools:visibility="visible" /> - + + android:visibility="visible" + app:constraint_referenced_ids="share_process_exp_date_divider, + share_radio_group, share_process_permission_title, + 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, divider_sharing_adv_permissions, + divider_sharing_permissions, divider_sharing_allow_resharing, + divider_sharing_change_name, divider_sharing_enter_password, + divider_sharing_exp_date,divider_sharing_hide_download, + share_file_drop_info, share_process_allow_resharing_info, + share_process_download_limit_switch, divider_sharing_download_limit, + share_process_download_limit_et,share_process_remaining_download_count_tv" /> - - + + + android:layout_marginRight="@dimen/standard_margin" + android:background="@drawable/share_et_bg" + android:gravity="top" + android:importantForAutofill="no" + android:inputType="textCapSentences|textMultiLine|textNoSuggestions" + android:overScrollMode="always" + android:padding="@dimen/standard_padding" + android:scrollbarStyle="insideInset" + android:scrollbars="vertical" + android:textColor="@color/share_txt_color" + android:textColorHighlight="@color/et_highlight_color" + android:textColorHint="@color/grey_60" + android:textSize="@dimen/txt_size_15sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/share_process_message_title" /> - - - + - - - - - - + android:visibility="gone" + app:constraint_referenced_ids="share_process_message_title, note_text" /> - + - + - + - + + diff --git a/app/src/main/res/layout/item_quick_share_permissions.xml b/app/src/main/res/layout/item_quick_share_permissions.xml index d40064e490be..4abbc64b3c34 100644 --- a/app/src/main/res/layout/item_quick_share_permissions.xml +++ b/app/src/main/res/layout/item_quick_share_permissions.xml @@ -2,23 +2,39 @@ - + \ No newline at end of file + android:padding="@dimen/standard_padding"> + + + + + + diff --git a/app/src/main/res/layout/list_item.xml b/app/src/main/res/layout/list_item.xml index fdc82c874448..48096a9d8fd3 100644 --- a/app/src/main/res/layout/list_item.xml +++ b/app/src/main/res/layout/list_item.xml @@ -251,18 +251,32 @@ android:visibility="gone" tools:visibility="visible" /> + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-de/nmc_sharing_strings.xml b/app/src/main/res/values-de/nmc_sharing_strings.xml new file mode 100644 index 000000000000..fb6b8f964443 --- /dev/null +++ b/app/src/main/res/values-de/nmc_sharing_strings.xml @@ -0,0 +1,55 @@ + + + Freigabe aufheben + Erstellen erlauben + Löschen erlauben + Bearbeitung erlauben + Nur Lesen + Link zum Ordner + Link zur Datei + Passwortschutz (%1$s) + Sie müssen das Ablaufdatum auswählen. + Bitte Anmerkung eingeben. + Email senden + Öffnen mit… + Sie können Links erstellen oder Freigaben per Mail versenden. Wenn Sie + MagentaCLOUD Nutzer einladen, bieten sich Ihnen mehr Möglichkeiten der Zusammenarbeit. + Ihre Nachricht + persönliche Freigabe per E-Mail + Neuen Link erstellen + Ihre Freigaben + Linkbezeichnung + Ihre Linkbezeichnung + Bei der Sammelbox ist nur das Hochladen erlaubt. Nur Sie sehen Dateien und Ordner die hochgeladen worden sind. + Password Protection + Expiration Date + Sie haben Ihre Datei / Ihren Ordner noch nicht geteilt. Teilen Sie um anderen Zugriff zu geben. + Download Limit + Das Feld für das Download-Limit darf nicht leer sein. + Downlimit eingeben + Downloads: %s + Der Wert für das Downloadlimit sollte größer als 0 sein. + Sie teilen mit einer/einem MagentaCLOUD Nutzer(in). Sie können ihr oder ihm erlauben, den Ordner oder die Dateien weiterzuteilen. + Der Passwortschutz ist aktiviert. Sie müssen dem Empfänger das Passwort + selbst mitteilen.\n\nWenn Sie die Freigabe über die MagentaCLOUD verschicken und das Passwort in den + Nachrichtentext eintragen, wird es unverschlüsselt im Klartext übertragen. + Empfangen + Geteilt + Link per E-Mail versenden + Weiterteilen wurde nicht erlaubt. + Weiterteilen ist erlaubt. + Dieser Datei / dieser Ordner wurde mit ihnen geteilt von %s + oder + Geteilt mit + Details + Jeder kann nur anzeigen + Nur anzeigen + Jeder kann bearbeiten + Nur bearbeiten + Jeder kann nur hochladen + Nur hochladen + Erweiterte Berechtigungen + Hochladen & Bearbeiten + Sammelbox + Weiterteilen erlauben + \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 6bb94fc8f45d..720a90836662 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -10,7 +10,7 @@ Bearbeiten Alle Benachrichtigungen löschen Papierkorb leeren - Senden/Teilen + Teilen Kachelansicht Listenansicht Kontakte und Kalender wiederherstellen @@ -214,7 +214,6 @@ URL konnte nicht abgerufen werden Erstellen Verzeichnis konnte nicht erstellt werden - Link erstellen Neu Neues Dokument Neuer Ordner @@ -529,7 +528,7 @@ Link Link-Name Link wird aufgrund von Sicherheitseinstellungen nicht verfolgt. - Bearbeitung + Bearbeiten Layout der Liste Weitere Ergebnisse laden Es befinden sich keine Dateien in diesem Ordner. @@ -601,7 +600,7 @@ Nur ein Konto zulässig Keine App zum Öffnen von PDFs verfügbar Keine App zum Senden der ausgewählten Dateien verfügbar - Bitte mindestens eine Berechtigung zum Teilen auswählen. + Bitte wählen Sie mindestens eine Berechtigung zum Teilen aus. Notiz konnte nicht versandt werden Notiz-Symbol Aktion konnte nicht ausgeführt werden @@ -776,7 +775,7 @@ Kontolöschung anfordern Löschung anfordern Beim Diensteanbieter die dauerhafte Löschung des Kontos anfordern - Richtlinien oder Berechtigungen verhindern das Weiterteilen + Resharing/Wiederteilen ist nicht erlaubt. Datei wiederherstellen Letzte Sicherung wiederherstellen Gelöschte Datei wiederherstellen @@ -842,21 +841,21 @@ diese Datei zu teilen Passwort eingeben (optional) Passwort eingeben - Teile Link (%1$s) + Link \"%1$s\" Ablaufdatum setzen Passwort setzen Erneutes teilen ist für die sichere Dateiablage nicht zugelassen Bitte mindestens eine Freigabeoption wählen, bevor Sie fortfahren. - Kann bearbeiten - Dateianfrage + Kann Bearbeiten + Sammelbox Sichere Dateiablage - Nur anzeigen + Nur lesen Berechtigungen zum Teilen Teilen Lesen %1$s (remote) %1$s (Unterhaltung) - Name, Federated-Cloud-ID oder E-Mail-Adresse … + Kontaktname oder E-Mail Benutzer oder Teams hinzufügen Neue E-Mail senden Notiz an Empfänger diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index db1e1d218038..e51fe9edd7db 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -39,4 +39,69 @@ @android:color/white #101418 + + + #FFFFFF + @color/grey_30 + @color/grey_30 + #CCCCCC + @color/grey_70 + @color/grey_80 + #2D2D2D + @color/grey_70 + @color/grey_70 + + + @color/grey_80 + @color/grey_0 + + + @color/grey_80 + @color/grey_0 + + + @color/grey_60 + @color/grey_0 + @color/grey_0 + @color/grey_30 + #FFFFFF + @color/grey_30 + @color/grey_80 + #FFFFFF + + + @color/grey_80 + @color/grey_30 + @color/grey_0 + + + @color/grey_80 + @color/grey_0 + @color/grey_80 + + + @color/grey_70 + @color/grey_60 + + + @color/grey_70 + @color/grey_70 + + + #FFFFFF + @color/grey_30 + @color/grey_0 + @color/grey_0 + @color/grey_0 + @color/grey_0 + @color/grey_60 + @color/grey_0 + #FFFFFF + #7d94f9 + + + #121212 + @color/grey_0 + @color/grey_80 + @color/grey_80 diff --git a/app/src/main/res/values-sw480dp/bool.xml b/app/src/main/res/values-sw480dp/bool.xml new file mode 100644 index 000000000000..8e66f10e898c --- /dev/null +++ b/app/src/main/res/values-sw480dp/bool.xml @@ -0,0 +1,4 @@ + + + true + diff --git a/app/src/main/res/values/bool.xml b/app/src/main/res/values/bool.xml new file mode 100644 index 000000000000..c2dcd8baf0ea --- /dev/null +++ b/app/src/main/res/values/bool.xml @@ -0,0 +1,4 @@ + + + false + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 36d7459ecdaf..5e5aafeb02d4 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -89,4 +89,94 @@ #A5A5A5 #F7F9FF + + + #191919 + @color/primary + #191919 + #191919 + @color/grey_30 + @android:color/white + #FFFFFF + @color/grey_0 + #CCCCCC + #77c4ff + #B3FFFFFF + @color/grey_10 + + + #101010 + #F2F2F2 + #E5E5E5 + #B2B2B2 + #666666 + #4C4C4C + #333333 + + + @color/design_snackbar_background_color + @color/white + + + #FFFFFF + #191919 + + + @color/grey_0 + #191919 + @color/primary + #191919 + @color/primary + @color/grey_30 + @color/white + #191919 + + + #FFFFFF + #191919 + #191919 + + + #FFFFFF + #191919 + #FFFFFF + + + @color/primary + #F399C7 + #FFFFFF + @color/grey_30 + @color/grey_10 + @color/grey_0 + + + @color/primary + @color/grey_30 + @color/grey_30 + #CCCCCC + + + #191919 + @color/grey_30 + #191919 + #191919 + #191919 + #191919 + @color/grey_30 + #191919 + #000000 + #191919 + #F6E5EB + #C16F81 + #0D39DF + #0099ff + #2238df + + + @color/grey_0 + #191919 + @color/grey_0 + @color/grey_30 + #77b6bb + #5077b6bb diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 000000000000..3a987ca9bef3 --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,32 @@ + + + 4dp + 16dp + 24dp + 6dp + 18sp + 15sp + 15dp + 56dp + 86dp + 80dp + 11sp + 30dp + 55dp + 258dp + 17sp + 20dp + 160dp + 50dp + 150dp + 55dp + 48dp + 48dp + 24dp + 26dp + 20sp + 145dp + 1dp + 13sp + 44dp + \ No newline at end of file diff --git a/app/src/main/res/values/nmc_sharing_strings.xml b/app/src/main/res/values/nmc_sharing_strings.xml new file mode 100644 index 000000000000..a735dfeca81f --- /dev/null +++ b/app/src/main/res/values/nmc_sharing_strings.xml @@ -0,0 +1,54 @@ + + + Unshare + Allow creating + Allow deleting + Allow editing + Read only + Link to folder + Link to file + Password protect (%1$s) + You must select expiration date. + Please enter note. + Send email + Open in… + You can create links or send shares by mail. If you invite MagentaCLOUD users, you have more opportunities for collaboration. + Your Message + Personal share by mail + Create new link + Your Shares + Link Label + Your custom link label + With File drop, only uploading is allowed. Only you can see files and folders that have been uploaded. + Password Protection + Expiration Date + You have not yet shared your file/folder. Share to give others access. + Download Limit + Download limit cannot be empty. + Enter download limit + Downloads: %s + Download limit should be greater than 0. + You are sharing with a MagentaCLOUD user and you can allow her or him to reshare. + Password protection has been enabled. You have to provide the password to + the recipient.\n\nIf you send a share via MagentaCLOUD and paste the password in this message, it will be + transmitted unencrypted in plaintext. + Received + Shared + Send link by mail + Resharing is not allowed. + Resharing is allowed. + This file / folder was shared with you by %s + or + Shared with + Detail + Everyone can only view + Only view + Everyone can edit + Can edit + Everyone can just upload + Just upload + Advanced Permissions + Allow upload and editing + File drop (upload only) + Allow resharing + \ No newline at end of file diff --git a/app/src/main/res/values/nmc_sharing_styles.xml b/app/src/main/res/values/nmc_sharing_styles.xml new file mode 100644 index 000000000000..a9d2e8f20905 --- /dev/null +++ b/app/src/main/res/values/nmc_sharing_styles.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7e699376c89e..c91f0edab8eb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -630,7 +630,7 @@ Share with… Unset - Name, Federated Cloud ID or email address… + Contact name or email Add users and teams Secure share… @@ -1083,8 +1083,8 @@ New %1$s %2$s - File request - View only + Filedrop only + Read only Can edit Secure file drop @@ -1104,7 +1104,7 @@ QR code could not be read! Note icon Add new public share link - Share link (%1$s) + Link \'%1$s\' Share link Custom permissions Read @@ -1179,7 +1179,7 @@ Create Please select one template Please choose a template and enter a file name. - Share permissions + Permissions Next Send share Please select at least one permission to share. @@ -1350,7 +1350,6 @@ Show less Internal shares External shares - Create link This folder is best viewed in %1$s. Open in %1$s This folder is already included in the parent folder’s sync, which may cause duplicate uploads diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 17cdbaabca4a..1f2acd054c3e 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -245,7 +245,8 @@ false - @@ -283,6 +284,7 @@