Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Posts List: Add Copy link functionality for posts #15570

Merged
merged 35 commits into from Nov 24, 2021
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
24417d1
Add new option to published post to copy the url to clipboard
Nov 11, 2021
323fdbd
Update unit tests for post actions
Nov 11, 2021
d0c7753
Add gridicon-link and use it for the copy url post button
Nov 13, 2021
470a605
Call invoke on triggerPostListAction for copy url
Nov 13, 2021
30117ab
Extract clipboard manager as context extension
Nov 13, 2021
fa4c053
Log clipboard manager exception post exception
Nov 13, 2021
471f7f0
Remove unused imports in PostListAction
Nov 13, 2021
889d7f8
Use post id as clipData label since it's only use is for developers
Nov 13, 2021
079352f
Show toast notification when the post link is copied
Nov 13, 2021
d48dd95
Refactor: Use addIf for BUTTON_COPY_URL to fix detekt err
Nov 13, 2021
a128bfa
Update detekt baseline
Nov 13, 2021
f556b13
Update release notes
Nov 13, 2021
c6f497f
Update release notes with additional newline
Nov 13, 2021
4d83750
Update release notes with correct link to pr
Nov 13, 2021
d54969b
Merge branch 'develop' into issue/13292-copy-published-post-url
Nov 15, 2021
f548f16
Fix typo in statusesDelimiter
Nov 15, 2021
4298845
Add proper annotation for one test in PostListItemUiStateHelperTest
Nov 15, 2021
8fac44d
Extract function to add delete or trash action button
Nov 15, 2021
6e3550e
Extract function to add view or preview button
Nov 15, 2021
6551903
Use conditional instead of addIf extension to add copyUrl button
Nov 15, 2021
48b7432
Fix redundant qualifier name errors in PostListItemUiStateHelper
Nov 15, 2021
0f2ad9e
Add missing post buttons test for case when stats are not supported
Nov 15, 2021
60c19e5
Use optional call to set primary clip and throw NPE otherwise
Nov 15, 2021
7398ab3
Explain why we handle generic exceptions in copy-to-clipboard code
Nov 15, 2021
67dd49d
Merge branch 'develop' into issue/13292-copy-published-post-url
Nov 15, 2021
e62023d
Move release note update to newest milestone
Nov 15, 2021
6ce9767
Add reference to chromium pr to catch for generic clipboard exceptions
Nov 16, 2021
f2d5238
Merge branch 'develop' into issue/13292-copy-published-post-url
Nov 17, 2021
ab2641d
Merge branch 'develop' into issue/13292-copy-published-post-url
Nov 23, 2021
a1bfa31
Show copy link action for posts of any status except trashed
Nov 23, 2021
f1c7f05
Show snackbar instead of toast when post link is copied to clipboard
Nov 23, 2021
82e8e8c
Merge branch 'develop' into issue/13292-copy-published-post-url
Nov 23, 2021
cf8aa2a
Merge branch 'develop' into issue/13292-copy-published-post-url
Nov 23, 2021
d5ad082
Merge branch 'develop' into issue/13292-copy-published-post-url
Nov 24, 2021
ca240a3
Fix indentation of comment in catch block
Nov 24, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -29,12 +29,12 @@ import org.wordpress.android.ui.posts.RemotePreviewLogicHelper.RemotePreviewType
import org.wordpress.android.ui.uploads.UploadService
import org.wordpress.android.ui.uploads.UploadUtils
import org.wordpress.android.ui.utils.UiString.UiStringRes
import org.wordpress.android.util.ToastUtils
import org.wordpress.android.util.ToastUtils.Duration
import org.wordpress.android.viewmodel.helpers.ToastMessageHolder
import org.wordpress.android.widgets.PostListButtonType
import org.wordpress.android.widgets.PostListButtonType.BUTTON_CANCEL_PENDING_AUTO_UPLOAD
import org.wordpress.android.widgets.PostListButtonType.BUTTON_COPY
import org.wordpress.android.widgets.PostListButtonType.BUTTON_COPY_URL
import org.wordpress.android.widgets.PostListButtonType.BUTTON_DELETE
import org.wordpress.android.widgets.PostListButtonType.BUTTON_DELETE_PERMANENTLY
import org.wordpress.android.widgets.PostListButtonType.BUTTON_EDIT
Expand Down Expand Up @@ -98,7 +98,7 @@ class PostActionHandler(
showToast = showToast,
messageMediaUploading = ToastMessageHolder(
R.string.editor_toast_uploading_please_wait,
ToastUtils.Duration.SHORT
Duration.SHORT
)
)
)
Expand All @@ -115,6 +115,7 @@ class PostActionHandler(
}
}
BUTTON_COPY -> copyPost(site, post, true)
BUTTON_COPY_URL -> triggerPostListAction.invoke(copyUrlAction(post))
BUTTON_DELETE, BUTTON_DELETE_PERMANENTLY -> {
postListDialogHelper.showDeletePostConfirmationDialog(post)
}
Expand All @@ -129,6 +130,20 @@ class PostActionHandler(
}
}

private fun copyUrlAction(post: PostModel) = PostListAction.CopyUrl(
site = site,
post = post,
showToast = showToast,
messageSuccess = ToastMessageHolder(
R.string.post_link_copied_to_clipboard,
Duration.SHORT
),
messageError = ToastMessageHolder(
R.string.error_copy_to_clipboard,
Duration.SHORT
)
)

private fun cancelPendingAutoUpload(post: PostModel) {
val msgRes = UploadUtils.cancelPendingAutoUpload(post, dispatcher)
showSnackbar.invoke(SnackbarMessageHolder(UiStringRes(msgRes)))
Expand Down
@@ -1,5 +1,6 @@
package org.wordpress.android.ui.posts

import android.content.ClipData
import androidx.fragment.app.FragmentActivity
import org.wordpress.android.fluxc.model.PostModel
import org.wordpress.android.fluxc.model.SiteModel
Expand All @@ -11,6 +12,8 @@ import org.wordpress.android.ui.posts.RemotePreviewLogicHelper.RemotePreviewType
import org.wordpress.android.ui.prefs.AppPrefs
import org.wordpress.android.ui.stories.intro.StoriesIntroDialogFragment
import org.wordpress.android.ui.uploads.UploadService
import org.wordpress.android.util.AppLog
import org.wordpress.android.util.clipboardManager
import org.wordpress.android.viewmodel.helpers.ToastMessageHolder

sealed class PostListAction {
Expand All @@ -33,6 +36,13 @@ sealed class PostListAction {
val post: PostModel,
val trackAnalytics: Boolean = PostUtils.isFirstTimePublish(post)
) : PostListAction()
class CopyUrl(
val site: SiteModel,
val post: PostModel,
val showToast: (ToastMessageHolder) -> Unit,
val messageSuccess: ToastMessageHolder,
val messageError: ToastMessageHolder
) : PostListAction()

class ViewStats(val site: SiteModel, val post: PostModel) : PostListAction()
class ViewPost(val site: SiteModel, val post: PostModel) : PostListAction()
Expand Down Expand Up @@ -91,5 +101,16 @@ fun handlePostListAction(
is PostListAction.DismissPendingNotification -> {
NativeNotificationsUtils.dismissNotification(action.pushId, activity)
}
is PostListAction.CopyUrl -> {
try {
activity.clipboardManager!!.setPrimaryClip(
antonis marked this conversation as resolved.
Show resolved Hide resolved
ClipData.newPlainText("${action.post.id}", action.post.link)
)
action.showToast.invoke(action.messageSuccess)
} catch (e: Exception) {
antonis marked this conversation as resolved.
Show resolved Hide resolved
AppLog.e(AppLog.T.POSTS, e)
action.showToast.invoke(action.messageError)
}
}
}
}
Expand Up @@ -7,6 +7,7 @@ import org.wordpress.android.util.analytics.AnalyticsUtils
import org.wordpress.android.widgets.PostListButtonType
import org.wordpress.android.widgets.PostListButtonType.BUTTON_CANCEL_PENDING_AUTO_UPLOAD
import org.wordpress.android.widgets.PostListButtonType.BUTTON_COPY
import org.wordpress.android.widgets.PostListButtonType.BUTTON_COPY_URL
import org.wordpress.android.widgets.PostListButtonType.BUTTON_DELETE
import org.wordpress.android.widgets.PostListButtonType.BUTTON_DELETE_PERMANENTLY
import org.wordpress.android.widgets.PostListButtonType.BUTTON_EDIT
Expand Down Expand Up @@ -49,6 +50,7 @@ fun trackPostListAction(site: SiteModel, buttonType: PostListButtonType, postDat
BUTTON_MOVE_TO_DRAFT -> "move_to_draft"
BUTTON_CANCEL_PENDING_AUTO_UPLOAD -> "cancel_pending_auto_upload"
BUTTON_SHOW_MOVE_TRASHED_POST_TO_DRAFT_DIALOG -> "show_move_trashed_post_to_draft_post_dialog"
BUTTON_COPY_URL -> "copy_url"
}

AnalyticsUtils.trackWithSiteDetails(statsEvent, site, properties)
Expand Down
@@ -1,5 +1,6 @@
package org.wordpress.android.util

import android.content.ClipboardManager
import android.content.Context
import android.content.res.ColorStateList
import android.util.TypedValue
Expand Down Expand Up @@ -41,3 +42,10 @@ fun Context.getColorStateListFromAttribute(@AttrRes attribute: Int): ColorStateL
// https://developer.android.com/reference/android/content/res/Configuration.html#locale
val Context.currentLocale: Locale
get() = ConfigurationCompat.getLocales(resources.configuration)[0]

/**
* Gets the clipboard manager system service
* @see android.content.ClipboardManager
*/
val Context.clipboardManager: ClipboardManager?
get() = ContextCompat.getSystemService(this, ClipboardManager::class.java)
ovitrif marked this conversation as resolved.
Show resolved Hide resolved
Expand Up @@ -46,6 +46,7 @@ import org.wordpress.android.viewmodel.uistate.ProgressBarUiState
import org.wordpress.android.widgets.PostListButtonType
import org.wordpress.android.widgets.PostListButtonType.BUTTON_CANCEL_PENDING_AUTO_UPLOAD
import org.wordpress.android.widgets.PostListButtonType.BUTTON_COPY
import org.wordpress.android.widgets.PostListButtonType.BUTTON_COPY_URL
import org.wordpress.android.widgets.PostListButtonType.BUTTON_DELETE
import org.wordpress.android.widgets.PostListButtonType.BUTTON_DELETE_PERMANENTLY
import org.wordpress.android.widgets.PostListButtonType.BUTTON_EDIT
Expand Down Expand Up @@ -156,7 +157,7 @@ class PostListItemUiStateHelper @Inject constructor(
uploadUiState = uploadUiState,
performingCriticalAction = performingCriticalAction
),
disableRippleEffect = postStatus == PostStatus.TRASHED
disableRippleEffect = postStatus == TRASHED
)

return PostListItemUiState(
Expand Down Expand Up @@ -380,7 +381,8 @@ class PostListItemUiStateHelper @Inject constructor(
!isLocalDraft &&
!isLocallyChanged
val canShowCopy = postStatus == PUBLISHED || postStatus == DRAFT
val canShowViewButton = !canRetryUpload && postStatus != PostStatus.TRASHED
val canShowCopyUrlButton = postStatus == PUBLISHED && !isLocalDraft
val canShowViewButton = !canRetryUpload && postStatus != TRASHED
val canShowPublishButton = canRetryUpload || canPublishPost
val buttonTypes = ArrayList<PostListButtonType>()

Expand All @@ -394,14 +396,11 @@ class PostListItemUiStateHelper @Inject constructor(

if (canShowPublishButton) {
buttonTypes.add(
if (canRetryUpload) {
BUTTON_RETRY
} else if (!siteHasCapabilitiesToPublish) {
BUTTON_SUBMIT
} else if (postStatus == SCHEDULED && isLocallyChanged) {
BUTTON_SYNC
} else {
BUTTON_PUBLISH
when {
canRetryUpload -> BUTTON_RETRY
!siteHasCapabilitiesToPublish -> BUTTON_SUBMIT
postStatus == SCHEDULED && isLocallyChanged -> BUTTON_SYNC
else -> BUTTON_PUBLISH
ovitrif marked this conversation as resolved.
Show resolved Hide resolved
}
)
}
Expand All @@ -426,6 +425,8 @@ class PostListItemUiStateHelper @Inject constructor(

buttonTypes.addMoveToDraftActionIfAvailable(postStatus)

buttonTypes.addIf(canShowCopyUrlButton, BUTTON_COPY_URL)

when {
isLocalDraft -> buttonTypes.add(BUTTON_DELETE)
postStatus == TRASHED -> {
Expand All @@ -437,6 +438,12 @@ class PostListItemUiStateHelper @Inject constructor(
return buttonTypes
}

private fun MutableList<PostListButtonType>.addIf(condition: Boolean, buttonType: PostListButtonType) {
antonis marked this conversation as resolved.
Show resolved Hide resolved
if (condition) {
add(buttonType)
}
}

private fun MutableList<PostListButtonType>.addMoveToDraftActionIfAvailable(postStatus: PostStatus) {
val canMovePostToDraft = postStatus == PUBLISHED || postStatus == TRASHED

Expand Down
Expand Up @@ -37,7 +37,8 @@ enum class PostListButtonType constructor(
R.attr.wpColorWarningDark
),
BUTTON_SHOW_MOVE_TRASHED_POST_TO_DRAFT_DIALOG(15, 0, 0, 0),
BUTTON_COPY(16, R.string.button_copy, R.drawable.ic_copy_white_24dp, R.attr.colorOnSurface);
BUTTON_COPY(16, R.string.button_copy, R.drawable.ic_copy_white_24dp, R.attr.colorOnSurface),
BUTTON_COPY_URL(17, R.string.button_copy_link, R.drawable.ic_gridicons_link_white_24dp, R.attr.colorOnSurface);

companion object {
fun fromInt(value: Int): PostListButtonType? = values().firstOrNull { it.value == value }
Expand Down
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="@android:color/white"
android:pathData="M17.001,12.999H7.001V10.999H17.001V12.999ZM18.001,6.999H17.001C15.37,6.999 13.936,7.791 13.024,8.999H18.001C19.104,8.999 20.001,9.896 20.001,10.999V12.999C20.001,14.102 19.104,14.999 18.001,14.999H13.024C13.937,16.207 15.371,16.999 17.001,16.999H18.001C19.0618,16.999 20.0793,16.5776 20.8294,15.8275C21.5795,15.0773 22.001,14.0599 22.001,12.999V10.999C22.001,9.9382 21.5795,8.9207 20.8294,8.1706C20.0793,7.4205 19.0618,6.999 18.001,6.999ZM2.001,10.999V12.999C2.001,14.0599 2.4224,15.0773 3.1725,15.8275C3.9227,16.5776 4.9401,16.999 6.001,16.999H7.001C8.631,16.999 10.066,16.207 10.978,14.999H6.001C4.898,14.999 4.001,14.102 4.001,12.999V10.999C4.001,9.896 4.898,8.999 6.001,8.999H10.978C10.066,7.791 8.632,6.999 7.001,6.999H6.001C4.9401,6.999 3.9227,7.4205 3.1725,8.1706C2.4224,8.9207 2.001,9.9382 2.001,10.999V10.999Z" />
</vector>
2 changes: 2 additions & 0 deletions WordPress/src/main/res/values/strings.xml
Expand Up @@ -309,6 +309,7 @@
<string name="button_retry">Retry</string>
<string name="button_move_to_draft">Move to Draft</string>
<string name="button_copy">Duplicate</string>
<string name="button_copy_link">Copy link</string>

<!-- post uploads -->
<string name="upload_failed_param">Upload failed for \"%s\"</string>
Expand Down Expand Up @@ -504,6 +505,7 @@
<string name="remove_account">Remove site</string>

<!-- post actions -->
<string name="post_link_copied_to_clipboard">Link copied to clipboard</string>
<string name="post_trashed">Post sent to trash</string>
<string name="post_trashing">Post is being trashed</string>
<string name="post_restored">Post restored</string>
Expand Down
Expand Up @@ -237,8 +237,9 @@ class PostListItemUiStateHelperTest {
assertThat(moreActions[0].buttonType).isEqualTo(PostListButtonType.BUTTON_STATS)
assertThat(moreActions[1].buttonType).isEqualTo(PostListButtonType.BUTTON_COPY)
assertThat(moreActions[2].buttonType).isEqualTo(PostListButtonType.BUTTON_MOVE_TO_DRAFT)
assertThat(moreActions[3].buttonType).isEqualTo(PostListButtonType.BUTTON_TRASH)
assertThat(moreActions).hasSize(4)
assertThat(moreActions[3].buttonType).isEqualTo(PostListButtonType.BUTTON_COPY_URL)
assertThat(moreActions[4].buttonType).isEqualTo(PostListButtonType.BUTTON_TRASH)
assertThat(moreActions).hasSize(5)
}

@Test
antonis marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -256,8 +257,9 @@ class PostListItemUiStateHelperTest {
assertThat(moreActions[0].buttonType).isEqualTo(PostListButtonType.BUTTON_PREVIEW)
assertThat(moreActions[1].buttonType).isEqualTo(PostListButtonType.BUTTON_COPY)
assertThat(moreActions[2].buttonType).isEqualTo(PostListButtonType.BUTTON_MOVE_TO_DRAFT)
assertThat(moreActions[3].buttonType).isEqualTo(PostListButtonType.BUTTON_TRASH)
assertThat(moreActions).hasSize(4)
assertThat(moreActions[3].buttonType).isEqualTo(PostListButtonType.BUTTON_COPY_URL)
assertThat(moreActions[4].buttonType).isEqualTo(PostListButtonType.BUTTON_TRASH)
assertThat(moreActions).hasSize(5)
}

@Test
Expand All @@ -277,8 +279,9 @@ class PostListItemUiStateHelperTest {
val moreActions = (state.actions[2] as MoreItem).actions
assertThat(moreActions[0].buttonType).isEqualTo(PostListButtonType.BUTTON_COPY)
assertThat(moreActions[1].buttonType).isEqualTo(PostListButtonType.BUTTON_MOVE_TO_DRAFT)
assertThat(moreActions[2].buttonType).isEqualTo(PostListButtonType.BUTTON_TRASH)
assertThat(moreActions).hasSize(3)
assertThat(moreActions[2].buttonType).isEqualTo(PostListButtonType.BUTTON_COPY_URL)
assertThat(moreActions[3].buttonType).isEqualTo(PostListButtonType.BUTTON_TRASH)
assertThat(moreActions).hasSize(4)
}

@Test
Expand Down Expand Up @@ -380,8 +383,9 @@ class PostListItemUiStateHelperTest {
assertThat(moreActions[0].buttonType).isEqualTo(PostListButtonType.BUTTON_PREVIEW)
assertThat(moreActions[1].buttonType).isEqualTo(PostListButtonType.BUTTON_COPY)
assertThat(moreActions[2].buttonType).isEqualTo(PostListButtonType.BUTTON_MOVE_TO_DRAFT)
assertThat(moreActions[3].buttonType).isEqualTo(PostListButtonType.BUTTON_TRASH)
assertThat(moreActions).hasSize(4)
assertThat(moreActions[3].buttonType).isEqualTo(PostListButtonType.BUTTON_COPY_URL)
assertThat(moreActions[4].buttonType).isEqualTo(PostListButtonType.BUTTON_TRASH)
assertThat(moreActions).hasSize(5)
}

@Test
Expand Down Expand Up @@ -467,8 +471,9 @@ class PostListItemUiStateHelperTest {
assertThat(moreActions[0].buttonType).isEqualTo(PostListButtonType.BUTTON_RETRY)
assertThat(moreActions[1].buttonType).isEqualTo(PostListButtonType.BUTTON_COPY)
assertThat(moreActions[2].buttonType).isEqualTo(PostListButtonType.BUTTON_MOVE_TO_DRAFT)
assertThat(moreActions[3].buttonType).isEqualTo(PostListButtonType.BUTTON_TRASH)
assertThat(moreActions).hasSize(4)
assertThat(moreActions[3].buttonType).isEqualTo(PostListButtonType.BUTTON_COPY_URL)
assertThat(moreActions[4].buttonType).isEqualTo(PostListButtonType.BUTTON_TRASH)
assertThat(moreActions).hasSize(5)
}

@Test
Expand Down
1 change: 1 addition & 0 deletions config/detekt/baseline.xml
Expand Up @@ -349,6 +349,7 @@
<ID>TooGenericExceptionCaught:ColorUnderlineSpan.kt$ColorUnderlineSpan$e: Exception</ID>
<ID>TooGenericExceptionCaught:GifMediaInsertUseCase.kt$GifMediaInsertUseCase$e: Exception</ID>
<ID>TooGenericExceptionCaught:MediaPickerActivity.kt$MediaPickerActivity$e: RuntimeException</ID>
<ID>TooGenericExceptionCaught:PostListAction.kt$e: Exception</ID>
<ID>TooGenericExceptionCaught:UploadStarter.kt$UploadStarter$e: Exception</ID>
<ID>TooGenericExceptionThrown:ActionableEmptyView.kt$ActionableEmptyView$throw RuntimeException("$context: ActionableEmptyView must have a title (aevTitle)")</ID>
<ID>TooGenericExceptionThrown:ActivityLogDetailFragment.kt$ActivityLogDetailFragment$throw Throwable("Couldn't initialize Activity Log view model")</ID>
Expand Down