This commit is contained in:
Mariotaku Lee 2017-05-26 19:07:45 +08:00
parent 647767e436
commit 039a022593
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
8 changed files with 78 additions and 24 deletions

View File

@ -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;
}
}

View File

@ -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("/")) {

View File

@ -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<SpanItem>.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)

View File

@ -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<SpanItem>, 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
}

View File

@ -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<SpanItem>?, 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] == '#'
}
}

View File

@ -653,7 +653,7 @@ abstract class AbsStatusesFragment : AbsContentListRecyclerViewFragment<Parcelab
val status = extras.getParcelable<ParcelableStatus>(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)

View File

@ -0,0 +1,28 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.text
import android.text.style.URLSpan
/**
* Created by mariotaku on 2017/5/26.
*/
class HashtagSpan(value: String) : URLSpan(value)

View File

@ -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<String, String>?) {
data class TagInfo(val start: Int, val name: String, val attributes: Map<String, String>?,
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<TagInfo>()
private val tagStack = ArrayList<TagInfo>()
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<String, String>?,
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 <p> tag
if ("p" == info.nameLower && "p" == lastTag?.nameLower) {
@ -181,7 +183,7 @@ object HtmlSpanBuilder {
@Throws(ParseException::class)
override fun handleStandaloneElement(elementName: String, attributes: Map<String, String>?,
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)
}