1
0
mirror of https://github.com/TwidereProject/Twidere-Android synced 2025-02-17 04:00:48 +01:00

improved reply text logic

This commit is contained in:
Mariotaku Lee 2017-04-02 15:12:37 +08:00
parent ec6ba7b4e0
commit 318fedf6db
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
6 changed files with 106 additions and 72 deletions

View File

@ -21,42 +21,27 @@
package org.mariotaku.microblog.library.twitter.model;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.mariotaku.restfu.RestFuUtils;
import org.mariotaku.restfu.http.SimpleValueMap;
public class StatusUpdate extends SimpleValueMap {
public StatusUpdate(final String status) {
public StatusUpdate(@NonNull final String status) {
put("status", status);
}
public void setInReplyToStatusId(final String inReplyToStatusId) {
put("in_reply_to_status_id", inReplyToStatusId);
}
public void setRepostStatusId(final String repostStatusId) {
public StatusUpdate repostStatusId(final String repostStatusId) {
put("repost_status_id", repostStatusId);
}
public void setMediaIds(final String... mediaIds) {
remove("media_ids");
if (mediaIds == null) return;
put("media_ids", RestFuUtils.toString(mediaIds, ','));
}
public void setAttachmentUrl(final String attachmentUrl) {
put("attachment_url", attachmentUrl);
}
public void setPlaceId(final String placeId) {
put("place_id", placeId);
}
public StatusUpdate inReplyToStatusId(final String inReplyToStatusId) {
setInReplyToStatusId(inReplyToStatusId);
return this;
}
public StatusUpdate inReplyToStatusId(final String inReplyToStatusId) {
put("in_reply_to_status_id", inReplyToStatusId);
return this;
}
public StatusUpdate displayCoordinates(final boolean displayCoordinates) {
put("display_coordinates", displayCoordinates);
@ -68,32 +53,42 @@ public class StatusUpdate extends SimpleValueMap {
return this;
}
public StatusUpdate excludeReplyUserIds(final String[] ids) {
put("exclude_reply_userids", RestFuUtils.toString(ids, ','));
public StatusUpdate excludeReplyUserIds(@Nullable final String[] ids) {
if (ids == null) {
remove("exclude_reply_userids");
} else {
put("exclude_reply_userids", RestFuUtils.toString(ids, ','));
}
return this;
}
public StatusUpdate location(final GeoLocation location) {
remove("lat");
remove("long");
if (location == null) return this;
put("lat", location.getLatitude());
put("long", location.getLongitude());
if (location == null) {
remove("lat");
remove("long");
} else {
put("lat", location.getLatitude());
put("long", location.getLongitude());
}
return this;
}
public StatusUpdate mediaIds(final String... mediaIds) {
setMediaIds(mediaIds);
public StatusUpdate mediaIds(@Nullable final String[] mediaIds) {
if (mediaIds == null) {
remove("media_ids");
} else {
put("media_ids", RestFuUtils.toString(mediaIds, ','));
}
return this;
}
public StatusUpdate placeId(final String placeId) {
setPlaceId(placeId);
put("place_id", placeId);
return this;
}
public StatusUpdate attachmentUrl(final String attachmentUrl) {
setAttachmentUrl(attachmentUrl);
put("attachment_url", attachmentUrl);
return this;
}

View File

@ -57,6 +57,7 @@ class ExtractorExtensionsKtTest {
Assert.assertEquals("lol", it.replyText)
Assert.assertTrue("extraMentions.isEmpty()", it.extraMentions.isEmpty())
Assert.assertTrue("excludedMentions.isEmpty()", it.excludedMentions.isEmpty())
Assert.assertTrue("replyToOriginalUser", it.replyToOriginalUser)
}
}
@ -68,6 +69,7 @@ class ExtractorExtensionsKtTest {
Assert.assertEquals("@mariotaku lol", it.replyText)
Assert.assertTrue("extraMentions.isEmpty()", it.extraMentions.entitiesContainsAll("mariotaku"))
Assert.assertTrue("excludedMentions.isEmpty()", it.excludedMentions.isEmpty())
Assert.assertTrue("replyToOriginalUser", it.replyToOriginalUser)
}
}
@ -78,6 +80,7 @@ class ExtractorExtensionsKtTest {
Assert.assertEquals("lol @mariotaku", it.replyText)
Assert.assertTrue("extraMentions.isEmpty()", it.extraMentions.entitiesContainsAll("mariotaku"))
Assert.assertTrue("excludedMentions.isEmpty()", it.excludedMentions.isEmpty())
Assert.assertTrue("replyToOriginalUser", it.replyToOriginalUser)
}
}
@ -89,6 +92,7 @@ class ExtractorExtensionsKtTest {
Assert.assertTrue("extraMentions.isEmpty()", it.extraMentions.isEmpty())
Assert.assertTrue("excludedMentions.containsAll(expectation)",
it.excludedMentions.mentionsContainsAll("nixcraft"))
Assert.assertTrue("replyToOriginalUser", it.replyToOriginalUser)
}
}
@ -103,6 +107,7 @@ class ExtractorExtensionsKtTest {
})
Assert.assertTrue("excludedMentions.containsAll(expectation)",
it.excludedMentions.mentionsContainsAll("nixcraft"))
Assert.assertTrue("replyToOriginalUser", it.replyToOriginalUser)
}
}
@ -115,6 +120,7 @@ class ExtractorExtensionsKtTest {
it.extraMentions.entitiesContainsAll("mariotaku"))
Assert.assertTrue("excludedMentions.containsAll(expectation)",
it.excludedMentions.mentionsContainsAll("nixcraft"))
Assert.assertTrue("replyToOriginalUser", it.replyToOriginalUser)
}
}
@ -125,6 +131,7 @@ class ExtractorExtensionsKtTest {
Assert.assertEquals("@nixcraft lol", it.replyText)
Assert.assertTrue("extraMentions.isEmpty()", it.extraMentions.isEmpty())
Assert.assertTrue("excludedMentions.isEmpty()", it.excludedMentions.isEmpty())
Assert.assertFalse("replyToOriginalUser", it.replyToOriginalUser)
}
}
@ -136,6 +143,7 @@ class ExtractorExtensionsKtTest {
Assert.assertTrue("extraMentions.containsAll(expectation)",
it.extraMentions.entitiesContainsAll("mariotaku"))
Assert.assertTrue("excludedMentions.isEmpty()", it.excludedMentions.isEmpty())
Assert.assertFalse("replyToOriginalUser", it.replyToOriginalUser)
}
}

View File

@ -80,7 +80,6 @@ import org.mariotaku.twidere.constant.IntentConstants.EXTRA_SCREEN_NAME
import org.mariotaku.twidere.extension.applyTheme
import org.mariotaku.twidere.extension.loadProfileImage
import org.mariotaku.twidere.extension.model.getAccountType
import org.mariotaku.twidere.extension.model.getAccountUser
import org.mariotaku.twidere.extension.model.textLimit
import org.mariotaku.twidere.extension.model.unique_id_non_null
import org.mariotaku.twidere.extension.text.twitter.getTweetLength
@ -1087,24 +1086,28 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
private fun handleReplyIntent(status: ParcelableStatus?): Boolean {
if (status == null) return false
val am = AccountManager.get(this)
val accountUser = AccountUtils.findByAccountKey(am, status.account_key)?.getAccountUser(am) ?: return false
val details = AccountUtils.getAccountDetails(am, status.account_key, false) ?: return false
val accountUser = details.user
var selectionStart = 0
val mentions = ArrayList<String>()
// If replying status from current user, just exclude it's screen name from selection.
if (accountUser.key != status.user_key) {
if (details.type == AccountType.TWITTER) {
// For Twitter, status user should always be the first
editText.append("@${status.user_screen_name} ")
} else if (accountUser.key != status.user_key) {
// If replying status from current user, just exclude it's screen name from selection.
editText.append("@${status.user_screen_name} ")
selectionStart = editText.length()
}
selectionStart = editText.length()
if (status.is_retweet && !TextUtils.isEmpty(status.retweeted_by_user_screen_name)) {
mentions.add(status.retweeted_by_user_screen_name)
}
if (status.is_quote && !TextUtils.isEmpty(status.quoted_user_screen_name)) {
mentions.add(status.quoted_user_screen_name)
}
if (!ArrayUtils.isEmpty(status.mentions)) {
status.mentions
.filterNot { it.key == status.account_key || it.screen_name.isNullOrEmpty() }
.mapTo(mentions) { it.screen_name }
if (status.mentions.isNotNullOrEmpty()) {
status.mentions.filterNot {
it.key == status.account_key || it.screen_name.isNullOrEmpty()
}.mapTo(mentions) { it.screen_name }
mentions.addAll(extractor.extractMentionedScreennames(status.quoted_text_plain))
} else if (USER_TYPE_FANFOU_COM == status.account_key.host) {
addFanfouHtmlToMentions(status.text_unescaped, status.spans, mentions)
@ -1119,12 +1122,15 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
}
mentions.distinctBy { it.toLowerCase(Locale.US) }.filterNot {
it.equals(status.user_screen_name, ignoreCase = true)
|| it.equals(accountUser.screen_name, ignoreCase = true)
if (details.type == AccountType.TWITTER && it.equals(accountUser.screen_name,
ignoreCase = true)) {
return@filterNot true
}
return@filterNot it.equals(status.user_screen_name, ignoreCase = true)
}.forEach { editText.append("@$it ") }
// Put current user mention at last
if (accountUser.key == status.user_key) {
// For non-Twitter instances, put current user mention at last
if (details.type != AccountType.TWITTER && accountUser.key == status.user_key) {
selectionStart = editText.length()
editText.append("@${status.user_screen_name} ")
}

View File

@ -38,25 +38,38 @@ fun Extractor.extractMentionsAndNonMentionStartIndex(text: String): MentionsAndN
fun Extractor.extractReplyTextAndMentions(text: String, inReplyTo: ParcelableStatus): ReplyTextAndMentions {
// First extract mentions and 'real text' start index
val (mentions, index) = extractMentionsAndNonMentionStartIndex(text)
val (textMentions, index) = extractMentionsAndNonMentionStartIndex(text)
val replyMentions = run {
val mentions = inReplyTo.mentions?.toMutableList() ?: mutableListOf()
if (inReplyTo.is_retweet) {
mentions.add(ParcelableUserMention().also {
it.key = inReplyTo.retweeted_by_user_key
it.name = inReplyTo.retweeted_by_user_name
it.screen_name = inReplyTo.retweeted_by_user_screen_name
})
}
return@run mentions
}
// Find mentions that `inReplyTo` doesn't have and add to `extraMentions` list
val extraMentions = mentions.filter { entity ->
val extraMentions = textMentions.filter { entity ->
if (entity.value.equals(inReplyTo.user_screen_name, ignoreCase = true)) {
return@filter false
}
return@filter inReplyTo.mentions?.none { mention ->
entity.value.equals(mention.screen_name, ignoreCase = true)
} ?: true
}
// Find removed mentions from `inReplyTo` and add to `excludedMentions` list
val excludedMentions = inReplyTo.mentions?.filter { mention ->
return@filter mentions.none { entity ->
return@filter replyMentions.none { mention ->
entity.value.equals(mention.screen_name, ignoreCase = true)
}
}.orEmpty()
}
// Find removed mentions from `inReplyTo` and add to `excludedMentions` list
val excludedMentions = replyMentions.filter { mention ->
return@filter textMentions.none { entity ->
entity.value.equals(mention.screen_name, ignoreCase = true)
}
}
// Find reply text contains mention to `inReplyTo.user`
val mentioningUser = mentions.any {
val mentioningUser = textMentions.any {
it.value.equals(inReplyTo.user_screen_name, ignoreCase = true)
}
if (!mentioningUser) {
@ -65,7 +78,7 @@ fun Extractor.extractReplyTextAndMentions(text: String, inReplyTo: ParcelableSta
* then this status should be treated at a mention referring to `inReplyTo`, all other mentions
* counts.
*/
return ReplyTextAndMentions(text, emptyList(), emptyList())
return ReplyTextAndMentions(text, emptyList(), emptyList(), mentioningUser)
}
val overrideText = run {
val sb = StringBuilder()
@ -78,7 +91,7 @@ fun Extractor.extractReplyTextAndMentions(text: String, inReplyTo: ParcelableSta
sb.append(text, index, text.length)
return@run sb.toString()
}
return ReplyTextAndMentions(overrideText, extraMentions, excludedMentions)
return ReplyTextAndMentions(overrideText, extraMentions, excludedMentions, mentioningUser)
}
data class MentionsAndNonMentionStartIndex(val mentions: List<Extractor.Entity>, val index: Int)
@ -86,5 +99,6 @@ data class MentionsAndNonMentionStartIndex(val mentions: List<Extractor.Entity>,
data class ReplyTextAndMentions(
val replyText: String,
val extraMentions: List<Extractor.Entity>,
val excludedMentions: List<ParcelableUserMention>
val excludedMentions: List<ParcelableUserMention>,
val replyToOriginalUser: Boolean
)

View File

@ -33,7 +33,7 @@ fun Validator.getTweetLength(text: String, ignoreMentions: Boolean, inReplyTo: P
return getTweetLength(text)
}
val (replyText, _, _) = InternalExtractor.extractReplyTextAndMentions(text, inReplyTo)
val (replyText, _, _, _) = InternalExtractor.extractReplyTextAndMentions(text, inReplyTo)
return getTweetLength(replyText)
}

View File

@ -116,7 +116,7 @@ class UpdateStatusTask(
* override status text, trim existing reply mentions, and add mentions that not
* exists in status text into ignore list
*/
regulateStatusText(update, pendingUpdate)
regulateReplyText(update, pendingUpdate)
val result: UpdateStatusResult
try {
@ -146,14 +146,21 @@ class UpdateStatusTask(
return result
}
private fun regulateStatusText(update: ParcelableStatusUpdate, pending: PendingStatusUpdate) {
private fun regulateReplyText(update: ParcelableStatusUpdate, pending: PendingStatusUpdate) {
if (update.draft_action != Draft.Action.REPLY) return
val inReplyTo = update.in_reply_to_status ?: return
for (i in 0 until pending.length) {
if (update.accounts[i].type != AccountType.TWITTER) continue
val (replyText, _, excludedMentions) = extractor.extractReplyTextAndMentions(
pending.overrideTexts[i], inReplyTo)
val (replyText, _, excludedMentions, replyToOriginalUser) =
extractor.extractReplyTextAndMentions(pending.overrideTexts[i], inReplyTo)
pending.overrideTexts[i] = replyText
pending.excludeReplyUserIds[i] = excludedMentions.map { it.key.id }.toTypedArray()
pending.replyToOriginalUser[i] = replyToOriginalUser
// Fix status to at least make mentioned user know what status it is
if (!replyToOriginalUser && update.attachment_url == null) {
update.attachment_url = LinkCreator.getTwitterStatusLink(inReplyTo.user_screen_name,
inReplyTo.id).toString()
}
}
}
@ -415,15 +422,18 @@ class UpdateStatusTask(
if (inReplyToStatus != null) {
status.inReplyToStatusId(inReplyToStatus.id)
if (statusUpdate.accounts[index].type == AccountType.TWITTER) {
status.autoPopulateReplyMetadata(true)
val replyToOriginalUser = pendingUpdate.replyToOriginalUser[index]
status.autoPopulateReplyMetadata(replyToOriginalUser)
if (replyToOriginalUser) {
status.excludeReplyUserIds(pendingUpdate.excludeReplyUserIds[index])
}
}
}
if (statusUpdate.repost_status_id != null) {
status.setRepostStatusId(statusUpdate.repost_status_id)
status.repostStatusId(statusUpdate.repost_status_id)
}
if (statusUpdate.attachment_url != null) {
status.setAttachmentUrl(statusUpdate.attachment_url)
status.attachmentUrl(statusUpdate.attachment_url)
}
if (statusUpdate.location != null) {
status.location(ParcelableLocationUtils.toGeoLocation(statusUpdate.location))
@ -431,7 +441,7 @@ class UpdateStatusTask(
}
val mediaIds = pendingUpdate.mediaIds[index]
if (mediaIds != null) {
status.mediaIds(*mediaIds)
status.mediaIds(mediaIds)
}
if (statusUpdate.is_possibly_sensitive) {
status.possiblySensitive(statusUpdate.is_possibly_sensitive)
@ -540,6 +550,7 @@ class UpdateStatusTask(
val overrideTexts: Array<String> = Array(length) { defaultText }
val excludeReplyUserIds: Array<Array<String>?> = arrayOfNulls(length)
val replyToOriginalUser: BooleanArray = BooleanArray(length)
val mediaIds: Array<Array<String>?> = arrayOfNulls(length)
val mediaUploadResults: Array<MediaUploadResult?> = arrayOfNulls(length)