From 039a022593b1fced57fb389c385243f87b8b97ff Mon Sep 17 00:00:00 2001 From: Mariotaku Lee Date: Fri, 26 May 2017 19:07:45 +0800 Subject: [PATCH] fixed #850 --- .../org/mariotaku/twidere/model/SpanItem.java | 3 +- .../twidere/util/TwidereLinkify.java | 3 ++ .../extension/model/SpanItemExtensions.kt | 5 +++ .../extension/model/api/StatusExtensions.kt | 6 ++-- .../model/api/mastodon/StatusExtensions.kt | 31 +++++++++++++------ .../twidere/fragment/AbsStatusesFragment.kt | 2 +- .../org/mariotaku/twidere/text/HashtagSpan.kt | 28 +++++++++++++++++ .../mariotaku/twidere/util/HtmlSpanBuilder.kt | 24 +++++++------- 8 files changed, 78 insertions(+), 24 deletions(-) create mode 100644 twidere/src/main/kotlin/org/mariotaku/twidere/text/HashtagSpan.kt diff --git a/twidere.component.common/src/main/java/org/mariotaku/twidere/model/SpanItem.java b/twidere.component.common/src/main/java/org/mariotaku/twidere/model/SpanItem.java index dd06d0248..ca93745c0 100644 --- a/twidere.component.common/src/main/java/org/mariotaku/twidere/model/SpanItem.java +++ b/twidere.component.common/src/main/java/org/mariotaku/twidere/model/SpanItem.java @@ -93,11 +93,12 @@ public class SpanItem implements Parcelable { SpanItemParcelablePlease.writeToParcel(this, dest, flags); } - @IntDef({SpanType.HIDE, SpanType.LINK, SpanType.ACCT_MENTION}) + @IntDef({SpanType.HIDE, SpanType.LINK, SpanType.ACCT_MENTION, SpanType.HASHTAG}) @Retention(RetentionPolicy.SOURCE) public @interface SpanType { int HIDE = -1; int LINK = 0; int ACCT_MENTION = 1; + int HASHTAG = 2; } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/TwidereLinkify.java b/twidere/src/main/java/org/mariotaku/twidere/util/TwidereLinkify.java index 500343712..535318ed6 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/TwidereLinkify.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/TwidereLinkify.java @@ -33,6 +33,7 @@ import com.twitter.Regex; import org.mariotaku.twidere.Constants; import org.mariotaku.twidere.model.UserKey; import org.mariotaku.twidere.text.AcctMentionSpan; +import org.mariotaku.twidere.text.HashtagSpan; import org.mariotaku.twidere.text.TwidereURLSpan; import java.lang.annotation.Retention; @@ -193,6 +194,8 @@ public final class TwidereLinkify implements Constants { int linkType = type; if (span instanceof AcctMentionSpan) { linkType = LINK_TYPE_USER_ACCT; + } else if (span instanceof HashtagSpan) { + linkType = LINK_TYPE_HASHTAG; } else if (accountKey != null && USER_TYPE_FANFOU_COM.equals(accountKey.getHost())) { // Fix search path if (url.startsWith("/")) { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/SpanItemExtensions.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/SpanItemExtensions.kt index ba9d24da7..dfbc6a12a 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/SpanItemExtensions.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/SpanItemExtensions.kt @@ -24,6 +24,7 @@ import android.text.Spanned import android.text.style.URLSpan import org.mariotaku.twidere.model.SpanItem import org.mariotaku.twidere.text.AcctMentionSpan +import org.mariotaku.twidere.text.HashtagSpan import org.mariotaku.twidere.text.ZeroWidthSpan val SpanItem.length: Int get() = end - start @@ -39,6 +40,10 @@ fun Array.applyTo(spannable: Spannable) { spannable.setSpan(AcctMentionSpan(span.link), span.start, span.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) } + SpanItem.SpanType.HASHTAG -> { + spannable.setSpan(HashtagSpan(span.link), span.start, span.end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + } else -> { spannable.setSpan(URLSpan(span.link), span.start, span.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/api/StatusExtensions.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/api/StatusExtensions.kt index abbb96d6d..4919efcc9 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/api/StatusExtensions.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/api/StatusExtensions.kt @@ -38,6 +38,7 @@ import org.mariotaku.twidere.model.util.ParcelableLocationUtils import org.mariotaku.twidere.model.util.ParcelableMediaUtils import org.mariotaku.twidere.model.util.ParcelableStatusUtils.addFilterFlag import org.mariotaku.twidere.text.AcctMentionSpan +import org.mariotaku.twidere.text.HashtagSpan import org.mariotaku.twidere.util.HtmlBuilder import org.mariotaku.twidere.util.HtmlSpanBuilder import org.mariotaku.twidere.util.InternalTwitterContentUtils.getMediaUrl @@ -278,8 +279,9 @@ internal fun findByOrigRange(spans: Array, start: Int, end: Int): List internal inline val CharSequence.spanItems get() = (this as? Spanned)?.let { text -> text.getSpans(0, length, URLSpan::class.java).mapToArray { val item = it.toSpanItem(text) - if (it is AcctMentionSpan) { - item.type = SpanItem.SpanType.ACCT_MENTION + when (it) { + is AcctMentionSpan -> item.type = SpanItem.SpanType.ACCT_MENTION + is HashtagSpan -> item.type = SpanItem.SpanType.HASHTAG } return@mapToArray item } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/api/mastodon/StatusExtensions.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/api/mastodon/StatusExtensions.kt index 93f2991b0..f7dbfbb15 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/api/mastodon/StatusExtensions.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/api/mastodon/StatusExtensions.kt @@ -29,6 +29,7 @@ import org.mariotaku.twidere.extension.model.api.spanItems import org.mariotaku.twidere.model.* import org.mariotaku.twidere.model.util.ParcelableStatusUtils.addFilterFlag import org.mariotaku.twidere.text.AcctMentionSpan +import org.mariotaku.twidere.text.HashtagSpan import org.mariotaku.twidere.util.HtmlEscapeHelper import org.mariotaku.twidere.util.HtmlSpanBuilder import org.mariotaku.twidere.util.emoji.EmojioneTranslator @@ -132,21 +133,33 @@ private fun calculateDisplayTextRange(text: String, spans: Array?, med object MastodonSpanProcessor : HtmlSpanBuilder.SpanProcessor { - override fun appendText(text: Editable, buffer: CharArray, start: Int, len: Int): Boolean { + override fun appendText(text: Editable, buffer: CharArray, start: Int, len: Int, + info: HtmlSpanBuilder.TagInfo?): Boolean { val unescaped = HtmlEscapeHelper.unescape(String(buffer, start, len)) text.append(EmojioneTranslator.translate(unescaped)) return true } override fun applySpan(text: Editable, start: Int, end: Int, info: HtmlSpanBuilder.TagInfo): Boolean { - val clsAttr = info.getAttribute("class") ?: return false - val hrefAttr = info.getAttribute("href") ?: return false - // Is mention or hashtag - if ("mention" !in clsAttr.split(" ")) return false - if (text[start] != '@') return false - text.setSpan(AcctMentionSpan(text.substring(start + 1, end), Uri.parse(hrefAttr).host), - start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - return true + val clsAttr = info.getAttribute("class")?.split(" ") ?: emptyList() + when { + "tag" in clsAttr || info.isTag(text) -> { + text.setSpan(HashtagSpan(text.substring(start, end)), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + return true + } + "mention" in clsAttr -> { + val hrefAttr = info.getAttribute("href") ?: return false + if (text[start] != '@') return false + text.setSpan(AcctMentionSpan(text.substring(start + 1, end), Uri.parse(hrefAttr).host), + start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + return true + } + } + return false } + fun HtmlSpanBuilder.TagInfo.isTag(text: CharSequence): Boolean { + return start > 0 && text[start - 1] == '#' + } } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/AbsStatusesFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/AbsStatusesFragment.kt index 9907c8fc5..1c3735dc2 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/AbsStatusesFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/AbsStatusesFragment.kt @@ -653,7 +653,7 @@ abstract class AbsStatusesFragment : AbsContentListRecyclerViewFragment(EXTRA_STATUS) if (status.account_key.host != accountKey.host) { val composeIntent = Intent(fragment.context, ComposeActivity::class.java) - composeIntent.putExtra(Intent.EXTRA_TEXT, " ${LinkCreator.getStatusWebLink(status)}") + composeIntent.putExtra(Intent.EXTRA_TEXT, "${status.text_plain } ${LinkCreator.getStatusWebLink(status)}") composeIntent.putExtra(EXTRA_ACCOUNT_KEY, accountKey) composeIntent.putExtra(EXTRA_SELECTION, 0) fragment.startActivity(composeIntent) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/text/HashtagSpan.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/text/HashtagSpan.kt new file mode 100644 index 000000000..491eca5a4 --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/text/HashtagSpan.kt @@ -0,0 +1,28 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * 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 org.mariotaku.twidere.text + +import android.text.style.URLSpan + +/** + * Created by mariotaku on 2017/5/26. + */ + +class HashtagSpan(value: String) : URLSpan(value) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/HtmlSpanBuilder.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/HtmlSpanBuilder.kt index 70b7afb67..2485fe839 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/util/HtmlSpanBuilder.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/util/HtmlSpanBuilder.kt @@ -100,7 +100,7 @@ object HtmlSpanBuilder { * @param start Start index of text to append in [buffer] * @param len Length of text to append in [buffer] */ - fun appendText(text: Editable, buffer: CharArray, start: Int, len: Int): Boolean = false + fun appendText(text: Editable, buffer: CharArray, start: Int, len: Int, info: TagInfo?): Boolean = false /** * @param text Text to apply span from [info] @@ -112,7 +112,8 @@ object HtmlSpanBuilder { } - data class TagInfo(val start: Int, val name: String, val attributes: Map?) { + data class TagInfo(val start: Int, val name: String, val attributes: Map?, + val parent: TagInfo? = null) { val nameLower = name.toLowerCase(Locale.US) @@ -138,11 +139,12 @@ object HtmlSpanBuilder { ) : AbstractSimpleMarkupHandler() { private val sb = SpannableStringBuilder() - private var tagInfo = ArrayList() + private val tagStack = ArrayList() private var lastTag: TagInfo? = null override fun handleText(buffer: CharArray, offset: Int, len: Int, line: Int, col: Int) { var cur = offset + val lastTag = this.lastTag while (cur < offset + len) { // Find first line break var lineBreakIndex = cur @@ -150,27 +152,27 @@ object HtmlSpanBuilder { if (buffer[lineBreakIndex] == '\n') break lineBreakIndex++ } - if (!(processor?.appendText(sb, buffer, cur, lineBreakIndex - cur) ?: false)) { + if (!(processor?.appendText(sb, buffer, cur, lineBreakIndex - cur, lastTag) ?: false)) { sb.append(HtmlEscapeHelper.unescape(String(buffer, cur, lineBreakIndex - cur))) } cur = lineBreakIndex + 1 } - lastTag = null + this.lastTag = null } override fun handleCloseElement(elementName: String, line: Int, col: Int) { - val lastIndex = lastIndexOfTag(tagInfo, elementName) + val lastIndex = lastIndexOfTag(tagStack, elementName) if (lastIndex == -1) return - val info = tagInfo[lastIndex] + val info = tagStack[lastIndex] applyTag(sb, info.start, sb.length, info, processor) - tagInfo.removeAt(lastIndex) + tagStack.removeAt(lastIndex) lastTag = info } override fun handleOpenElement(elementName: String, attributes: Map?, line: Int, col: Int) { - val info = TagInfo(sb.length, elementName, attributes) - tagInfo.add(info) + val info = TagInfo(sb.length, elementName, attributes, tagStack.lastOrNull()) + tagStack.add(info) // Mastodon case, insert 2 breaks between two

tag if ("p" == info.nameLower && "p" == lastTag?.nameLower) { @@ -181,7 +183,7 @@ object HtmlSpanBuilder { @Throws(ParseException::class) override fun handleStandaloneElement(elementName: String, attributes: Map?, minimized: Boolean, line: Int, col: Int) { - val info = TagInfo(sb.length, elementName, attributes) + val info = TagInfo(sb.length, elementName, attributes, tagStack.lastOrNull()) applyTag(sb, info.start, sb.length, info, processor) }