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

improved compose reply

This commit is contained in:
Mariotaku Lee 2017-04-14 12:19:21 +08:00
parent 5f58d0edce
commit 0b27aad645
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
7 changed files with 128 additions and 89 deletions

View File

@ -73,6 +73,12 @@ public class ParcelableStatusUpdate implements Parcelable {
@JsonField(name = "attachment_url")
@ParcelableThisPlease
public String attachment_url;
@JsonField(name = "excluded_reply_user_ids")
@ParcelableThisPlease
public String[] excluded_reply_user_ids;
@JsonField(name = "extended_reply_mode")
@ParcelableThisPlease
public boolean extended_reply_mode;
@JsonField(name = "draft_unique_id")
@ParcelableThisPlease
public String draft_unique_id;

View File

@ -51,6 +51,12 @@ public class UpdateStatusActionExtras implements ActionExtras {
@ParcelableThisPlease
@JsonField(name = "attachment_url")
String attachmentUrl;
@JsonField(name = "excluded_reply_user_ids")
@ParcelableThisPlease
String[] excludedReplyUserIds;
@JsonField(name = "extended_reply_mode")
@ParcelableThisPlease
boolean extendedReplyMode;
public ParcelableStatus getInReplyToStatus() {
return inReplyToStatus;
@ -92,6 +98,22 @@ public class UpdateStatusActionExtras implements ActionExtras {
this.attachmentUrl = attachmentUrl;
}
public String[] getExcludedReplyUserIds() {
return excludedReplyUserIds;
}
public void setExcludedReplyUserIds(final String[] excludedReplyUserIds) {
this.excludedReplyUserIds = excludedReplyUserIds;
}
public boolean isExtendedReplyMode() {
return extendedReplyMode;
}
public void setExtendedReplyMode(final boolean extendedReplyMode) {
this.extendedReplyMode = extendedReplyMode;
}
@Override
public int describeContents() {
return 0;

View File

@ -1 +1 @@
ea793d047ce90d44b4d0d8b67cb69c0b99ca173e
54042eb537a2d7133b28350a33773882de5fe472

View File

@ -76,6 +76,7 @@ import org.mariotaku.twidere.extension.applyTheme
import org.mariotaku.twidere.extension.loadProfileImage
import org.mariotaku.twidere.extension.model.textLimit
import org.mariotaku.twidere.extension.model.unique_id_non_null
import org.mariotaku.twidere.extension.text.twitter.ReplyTextAndMentions
import org.mariotaku.twidere.extension.text.twitter.extractReplyTextAndMentions
import org.mariotaku.twidere.extension.withAppendedPath
import org.mariotaku.twidere.fragment.*
@ -411,10 +412,14 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
}
private fun hasComposingStatus(): Boolean {
val text = if (editText != null) ParseUtils.parseString(editText.text) else null
val textChanged = text != null && !text.isEmpty() && text != originalText
val isEditingDraft = INTENT_ACTION_EDIT_DRAFT == intent.action
return textChanged || hasMedia() || isEditingDraft
if (intent.action == INTENT_ACTION_EDIT_DRAFT) return true
if (hasMedia()) return true
val text = editText.text?.toString().orEmpty()
val replyTextAndMentions = getTwitterReplyTextAndMentions(text)
if (replyTextAndMentions != null) {
return replyTextAndMentions.replyText.isNotEmpty()
}
return text.isNotEmpty() && text != originalText
}
override fun onSaveInstanceState(outState: Bundle) {
@ -1214,8 +1219,9 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
val accounts = accountsAdapter.selectedAccounts
editText.accountKey = accounts.firstOrNull()?.key ?: Utils.getDefaultAccountKey(this)
statusTextCount.maxLength = accounts.textLimit
ignoreMentions = accounts.all { it.type == AccountType.TWITTER }
replyToSelf = accounts.singleOrNull()?.let { it.key == inReplyToStatus?.user_key } ?: false
val singleAccount = accounts.singleOrNull()
ignoreMentions = singleAccount?.type == AccountType.TWITTER
replyToSelf = singleAccount?.let { it.key == inReplyToStatus?.user_key } ?: false
setMenu()
}
@ -1464,69 +1470,75 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
private fun updateStatus() {
if (isFinishing || editText == null) return
val hasMedia = hasMedia()
val text = editText.text.toString()
val accountKeys = accountsAdapter.selectedAccountKeys
val accounts = AccountUtils.getAllAccountDetails(AccountManager.get(this), accountKeys, true)
val ignoreMentions = accounts.all { it.type == AccountType.TWITTER }
val inReplyTo = inReplyToStatus
val maxLength = statusTextCount.maxLength
val tweetLength: Int
val exceededStartIndex: Int
if (inReplyTo != null && ignoreMentions) {
val (replyStartIndex, replyText) = extractor.extractReplyTextAndMentions(text,
inReplyTo)
tweetLength = validator.getTweetLength(replyText)
if (tweetLength > maxLength) {
exceededStartIndex = replyStartIndex + replyText.offsetByCodePoints(0, maxLength)
} else {
exceededStartIndex = -1
}
} else {
tweetLength = validator.getTweetLength(text)
if (tweetLength > maxLength) {
exceededStartIndex = text.offsetByCodePoints(0, maxLength)
} else {
exceededStartIndex = -1
}
}
if (accountsAdapter.isSelectionEmpty) {
val update = try {
getStatusUpdate()
} catch(e: NoAccountException) {
editText.error = getString(R.string.message_toast_no_account_selected)
return
} else if (!hasMedia && (tweetLength <= 0 || noReplyContent(text))) {
} catch(e: NoContentException) {
editText.error = getString(R.string.error_message_no_content)
return
} else if (maxLength > 0 && !statusShortenerUsed && tweetLength > maxLength) {
} catch(e: StatusTooLongException) {
editText.error = getString(R.string.error_message_status_too_long)
if (exceededStartIndex >= 0) {
editText.setSelection(exceededStartIndex, editText.length())
}
editText.setSelection(e.exceededStartIndex, editText.length())
return
}
LengthyOperationsService.updateStatusesAsync(this, update.draft_action, statuses = update,
scheduleInfo = scheduleInfo)
finishComposing()
}
private fun getStatusUpdate(): ParcelableStatusUpdate {
val accountKeys = accountsAdapter.selectedAccountKeys
if (accountKeys.isEmpty()) throw NoAccountException()
val update = ParcelableStatusUpdate()
val media = this.media
val text = editText.text?.toString().orEmpty()
val accounts = AccountUtils.getAllAccountDetails(AccountManager.get(this), accountKeys, true)
val maxLength = statusTextCount.maxLength
val replyTextAndMentions = getTwitterReplyTextAndMentions(text)
if (replyTextAndMentions != null) {
val (replyStartIndex, replyText, _, excludedMentions) = replyTextAndMentions
if (replyText.isEmpty() && media.isEmpty()) throw NoContentException()
if (!statusShortenerUsed && validator.getTweetLength(replyText) > maxLength) {
throw StatusTooLongException(replyStartIndex + replyText.offsetByCodePoints(0, maxLength))
}
update.text = replyText
update.extended_reply_mode = true
update.excluded_reply_user_ids = excludedMentions.map { it.key.id }.toTypedArray()
} else {
if (text.isEmpty() && media.isEmpty()) throw NoContentException()
if (!statusShortenerUsed && validator.getTweetLength(text) > maxLength) {
throw StatusTooLongException(text.offsetByCodePoints(0, maxLength))
}
update.text = text
update.extended_reply_mode = false
}
val attachLocation = kPreferences[attachLocationKey]
val attachPreciseLocation = kPreferences[attachPreciseLocationKey]
val isPossiblySensitive = hasMedia && possiblySensitive
val update = ParcelableStatusUpdate()
@Draft.Action val action = draft?.action_type ?: getDraftAction(intent.action)
update.draft_action = draft?.action_type ?: getDraftAction(intent.action)
update.accounts = accounts
update.text = text
if (attachLocation) {
update.location = recentLocation
update.display_coordinates = attachPreciseLocation
}
update.media = media
update.in_reply_to_status = inReplyToStatus
update.is_possibly_sensitive = isPossiblySensitive
update.is_possibly_sensitive = possiblySensitive
update.draft_extras = UpdateStatusActionExtras().also {
it.inReplyToStatus = inReplyToStatus
it.isPossiblySensitive = isPossiblySensitive
it.displayCoordinates = attachPreciseLocation
it.inReplyToStatus = update.in_reply_to_status
it.isPossiblySensitive = update.is_possibly_sensitive
it.displayCoordinates = update.display_coordinates
it.excludedReplyUserIds = update.excluded_reply_user_ids
it.isExtendedReplyMode = update.extended_reply_mode
}
return update
}
LengthyOperationsService.updateStatusesAsync(this, action, statuses = update,
scheduleInfo = scheduleInfo)
private fun finishComposing() {
if (preferences[noCloseAfterTweetSentKey] && inReplyToStatus == null) {
possiblySensitive = false
shouldSaveAccounts = true
@ -1552,28 +1564,34 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
private fun updateTextCount() {
val editable = editText.editableText ?: return
val inReplyTo = inReplyToStatus
val text = editable.toString()
val mentionColor = ThemeUtils.getColorFromAttribute(this, android.R.attr.textColorSecondary, 0)
if (inReplyTo != null && ignoreMentions) {
val textAndMentions = extractor.extractReplyTextAndMentions(text, inReplyTo)
if (textAndMentions.replyToOriginalUser || replyToSelf) {
hintLabel.visibility = View.GONE
editable.clearSpans(MentionColorSpan::class.java)
editable.setSpan(MentionColorSpan(mentionColor), 0, textAndMentions.replyStartIndex,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
} else {
hintLabel.visibility = View.VISIBLE
editable.clearSpans(MentionColorSpan::class.java)
}
statusTextCount.textCount = validator.getTweetLength(textAndMentions.replyText)
} else {
statusTextCount.textCount = validator.getTweetLength(text)
val textAndMentions = getTwitterReplyTextAndMentions(text)
if (textAndMentions == null) {
hintLabel.visibility = View.GONE
editable.clearSpans(MentionColorSpan::class.java)
statusTextCount.textCount = validator.getTweetLength(text)
} else if (textAndMentions.replyToOriginalUser || replyToSelf) {
hintLabel.visibility = View.GONE
val mentionColor = ThemeUtils.getColorFromAttribute(this,
android.R.attr.textColorSecondary, 0)
editable.clearSpans(MentionColorSpan::class.java)
editable.setSpan(MentionColorSpan(mentionColor), 0, textAndMentions.replyStartIndex,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
statusTextCount.textCount = validator.getTweetLength(textAndMentions.replyText)
} else {
hintLabel.visibility = View.VISIBLE
editable.clearSpans(MentionColorSpan::class.java)
statusTextCount.textCount = validator.getTweetLength(textAndMentions.replyText)
}
}
private fun getTwitterReplyTextAndMentions(text: String = editText.text?.toString().orEmpty()):
ReplyTextAndMentions? {
val inReplyTo = inReplyToStatus ?: return null
if (!ignoreMentions) return null
return extractor.extractReplyTextAndMentions(text, inReplyTo)
}
private fun updateUpdateStatusIcon() {
if (scheduleInfo != null) {
updateStatusIcon.setImageResource(R.drawable.ic_action_time)
@ -2053,6 +2071,10 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
private class MentionColorSpan(color: Int) : ForegroundColorSpan(color)
private class StatusTooLongException(val exceededStartIndex: Int) : Exception()
private class NoContentException() : Exception()
private class NoAccountException() : Exception()
companion object {
// Constants

View File

@ -102,7 +102,7 @@ data class MentionsAndNonMentionStartIndex(val mentions: List<Extractor.Entity>,
data class ReplyTextAndMentions(
val replyStartIndex: Int,
val replyText: String,
val extraMentions: List<Extractor.Entity>,
val excludedMentions: List<ParcelableUserMention>,
val replyToOriginalUser: Boolean
val extraMentions: List<Extractor.Entity> = emptyList(),
val excludedMentions: List<ParcelableUserMention> = emptyList(),
val replyToOriginalUser: Boolean = false
)

View File

@ -32,6 +32,8 @@ object ParcelableStatusUpdateUtils {
statusUpdate.is_possibly_sensitive = actionExtras.isPossiblySensitive
statusUpdate.display_coordinates = actionExtras.displayCoordinates
statusUpdate.attachment_url = actionExtras.attachmentUrl
statusUpdate.excluded_reply_user_ids = actionExtras.excludedReplyUserIds
statusUpdate.extended_reply_mode = actionExtras.isExtendedReplyMode
}
is QuoteStatusActionExtras -> {
val onlyAccount = statusUpdate.accounts.singleOrNull()

View File

@ -109,12 +109,6 @@ class UpdateStatusTask(
val pendingUpdate = PendingStatusUpdate(update)
/*
* override status text, trim existing reply mentions, and add mentions that not
* exists in status text into ignore list
*/
regulateReplyText(update, pendingUpdate)
val result: UpdateStatusResult
try {
uploadMedia(uploader, update, info, pendingUpdate)
@ -152,7 +146,7 @@ class UpdateStatusTask(
val (_, replyText, extraMentions, excludedMentions, replyToOriginalUser) =
extractor.extractReplyTextAndMentions(pending.overrideTexts[i], inReplyTo)
pending.overrideTexts[i] = replyText
pending.excludeReplyUserIds[i] = excludedMentions.mapNotNull { mention ->
val excludeReplyUserIds = excludedMentions.mapNotNull { mention ->
// Remove account mention that is not appeared in `extraMentions`
if (details.key == mention.key && extraMentions.none {
it.value.equals(mention.screen_name, ignoreCase = true)
@ -160,8 +154,6 @@ class UpdateStatusTask(
return@mapNotNull mention.key.id
}.toTypedArray()
val replyToSelf = details.key == inReplyTo.user_key
pending.replyToSelf[i] = replyToSelf
pending.replyToOriginalUser[i] = replyToOriginalUser
// Fix status to at least make mentioned user know what status it is
if (!replyToOriginalUser && !replyToSelf && update.attachment_url == null) {
update.attachment_url = LinkCreator.getTwitterStatusLink(inReplyTo.user_screen_name,
@ -423,16 +415,14 @@ class UpdateStatusTask(
val overrideText = pendingUpdate.overrideTexts[index]
val status = StatusUpdate(overrideText)
val inReplyToStatus = statusUpdate.in_reply_to_status
if (inReplyToStatus != null) {
status.inReplyToStatusId(inReplyToStatus.id)
if (statusUpdate.accounts[index].type == AccountType.TWITTER) {
val replyToOriginalUser = pendingUpdate.replyToOriginalUser[index]
val replyToSelf = pendingUpdate.replyToSelf[index]
status.autoPopulateReplyMetadata(replyToOriginalUser || replyToSelf)
val excludeReplyIds = pendingUpdate.excludeReplyUserIds[index]
if ((replyToOriginalUser || replyToSelf) && excludeReplyIds.isNotNullOrEmpty()) {
status.excludeReplyUserIds(excludeReplyIds)
}
val details = statusUpdate.accounts[index]
if (details.type == AccountType.TWITTER && statusUpdate.extended_reply_mode) {
status.autoPopulateReplyMetadata(true)
status.excludeReplyUserIds(statusUpdate.excluded_reply_user_ids)
}
}
if (statusUpdate.repost_status_id != null) {
@ -549,9 +539,6 @@ class UpdateStatusTask(
var sharedMediaOwners: Array<UserKey>? = null
val overrideTexts: Array<String> = Array(length) { defaultText }
val excludeReplyUserIds: Array<Array<String>?> = arrayOfNulls(length)
val replyToOriginalUser: BooleanArray = BooleanArray(length)
val replyToSelf: BooleanArray = BooleanArray(length)
val mediaIds: Array<Array<String>?> = arrayOfNulls(length)
val mediaUploadResults: Array<MediaUploadResult?> = arrayOfNulls(length)