mirror of
https://github.com/TwidereProject/Twidere-Android
synced 2025-02-09 00:08:41 +01:00
parent
97618595c6
commit
a3b03d105c
@ -30,7 +30,7 @@ subprojects {
|
|||||||
libVersions = [
|
libVersions = [
|
||||||
Kotlin : '1.1.1',
|
Kotlin : '1.1.1',
|
||||||
SupportLib : '25.3.1',
|
SupportLib : '25.3.1',
|
||||||
MariotakuCommons : '0.9.13',
|
MariotakuCommons : '0.9.14',
|
||||||
RestFu : '0.9.54',
|
RestFu : '0.9.54',
|
||||||
ObjectCursor : '0.9.16',
|
ObjectCursor : '0.9.16',
|
||||||
PlayServices : '10.2.1',
|
PlayServices : '10.2.1',
|
||||||
|
@ -21,8 +21,6 @@ package org.mariotaku.twidere.model;
|
|||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.support.annotation.IntDef;
|
import android.support.annotation.IntDef;
|
||||||
import android.text.Spanned;
|
|
||||||
import android.text.style.URLSpan;
|
|
||||||
|
|
||||||
import com.bluelinelabs.logansquare.annotation.JsonField;
|
import com.bluelinelabs.logansquare.annotation.JsonField;
|
||||||
import com.bluelinelabs.logansquare.annotation.JsonObject;
|
import com.bluelinelabs.logansquare.annotation.JsonObject;
|
||||||
@ -63,21 +61,23 @@ public class SpanItem implements Parcelable {
|
|||||||
@ParcelableThisPlease
|
@ParcelableThisPlease
|
||||||
public String link;
|
public String link;
|
||||||
|
|
||||||
|
@ParcelableThisPlease
|
||||||
|
@JsonField(name = "type")
|
||||||
|
@SpanType
|
||||||
|
public int type = SpanType.LINK;
|
||||||
|
|
||||||
@ParcelableNoThanks
|
@ParcelableNoThanks
|
||||||
public int orig_start = -1;
|
public int orig_start = -1;
|
||||||
@ParcelableNoThanks
|
@ParcelableNoThanks
|
||||||
public int orig_end = -1;
|
public int orig_end = -1;
|
||||||
|
|
||||||
@ParcelableNoThanks
|
|
||||||
@SpanType
|
|
||||||
public int type = SpanType.LINK;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "SpanItem{" +
|
return "SpanItem{" +
|
||||||
"start=" + start +
|
"start=" + start +
|
||||||
", end=" + end +
|
", end=" + end +
|
||||||
", link='" + link + '\'' +
|
", link='" + link + '\'' +
|
||||||
|
", type=" + type +
|
||||||
", orig_start=" + orig_start +
|
", orig_start=" + orig_start +
|
||||||
", orig_end=" + orig_end +
|
", orig_end=" + orig_end +
|
||||||
'}';
|
'}';
|
||||||
@ -93,18 +93,11 @@ public class SpanItem implements Parcelable {
|
|||||||
SpanItemParcelablePlease.writeToParcel(this, dest, flags);
|
SpanItemParcelablePlease.writeToParcel(this, dest, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SpanItem from(Spanned spanned, URLSpan span) {
|
@IntDef({SpanType.HIDE, SpanType.LINK, SpanType.ACCT_MENTION})
|
||||||
SpanItem spanItem = new SpanItem();
|
|
||||||
spanItem.link = span.getURL();
|
|
||||||
spanItem.start = spanned.getSpanStart(span);
|
|
||||||
spanItem.end = spanned.getSpanEnd(span);
|
|
||||||
return spanItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@IntDef({SpanType.HIDE, SpanType.LINK})
|
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
public @interface SpanType {
|
public @interface SpanType {
|
||||||
int HIDE = -1;
|
int HIDE = -1;
|
||||||
int LINK = 0;
|
int LINK = 0;
|
||||||
|
int ACCT_MENTION = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -194,6 +194,7 @@ dependencies {
|
|||||||
compile "com.github.mariotaku.CommonsLibrary:io:${libVersions['MariotakuCommons']}"
|
compile "com.github.mariotaku.CommonsLibrary:io:${libVersions['MariotakuCommons']}"
|
||||||
compile "com.github.mariotaku.CommonsLibrary:text:${libVersions['MariotakuCommons']}"
|
compile "com.github.mariotaku.CommonsLibrary:text:${libVersions['MariotakuCommons']}"
|
||||||
compile "com.github.mariotaku.CommonsLibrary:text-kotlin:${libVersions['MariotakuCommons']}"
|
compile "com.github.mariotaku.CommonsLibrary:text-kotlin:${libVersions['MariotakuCommons']}"
|
||||||
|
compile "com.github.mariotaku.CommonsLibrary:emojione:${libVersions['MariotakuCommons']}"
|
||||||
compile "com.github.mariotaku:KPreferences:${libVersions['KPreferences']}"
|
compile "com.github.mariotaku:KPreferences:${libVersions['KPreferences']}"
|
||||||
compile "com.github.mariotaku:Chameleon:${libVersions['Chameleon']}"
|
compile "com.github.mariotaku:Chameleon:${libVersions['Chameleon']}"
|
||||||
compile 'com.github.mariotaku.QR-Code-generator:core:fcab3ea7f4'
|
compile 'com.github.mariotaku.QR-Code-generator:core:fcab3ea7f4'
|
||||||
|
@ -23,7 +23,6 @@ import android.support.annotation.IntDef;
|
|||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.text.Spannable;
|
import android.text.Spannable;
|
||||||
import android.text.SpannableString;
|
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.text.style.URLSpan;
|
import android.text.style.URLSpan;
|
||||||
|
|
||||||
@ -33,6 +32,7 @@ import com.twitter.Regex;
|
|||||||
|
|
||||||
import org.mariotaku.twidere.Constants;
|
import org.mariotaku.twidere.Constants;
|
||||||
import org.mariotaku.twidere.model.UserKey;
|
import org.mariotaku.twidere.model.UserKey;
|
||||||
|
import org.mariotaku.twidere.text.AcctMentionSpan;
|
||||||
import org.mariotaku.twidere.text.TwidereURLSpan;
|
import org.mariotaku.twidere.text.TwidereURLSpan;
|
||||||
|
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
@ -69,6 +69,7 @@ public final class TwidereLinkify implements Constants {
|
|||||||
public static final int LINK_TYPE_LIST = 6;
|
public static final int LINK_TYPE_LIST = 6;
|
||||||
public static final int LINK_TYPE_CASHTAG = 7;
|
public static final int LINK_TYPE_CASHTAG = 7;
|
||||||
public static final int LINK_TYPE_USER_ID = 8;
|
public static final int LINK_TYPE_USER_ID = 8;
|
||||||
|
public static final int LINK_TYPE_USER_ACCT = 9;
|
||||||
|
|
||||||
public static final int[] ALL_LINK_TYPES = new int[]{LINK_TYPE_ENTITY_URL, LINK_TYPE_LINK_IN_TEXT,
|
public static final int[] ALL_LINK_TYPES = new int[]{LINK_TYPE_ENTITY_URL, LINK_TYPE_LINK_IN_TEXT,
|
||||||
LINK_TYPE_MENTION, LINK_TYPE_HASHTAG, LINK_TYPE_CASHTAG};
|
LINK_TYPE_MENTION, LINK_TYPE_HASHTAG, LINK_TYPE_CASHTAG};
|
||||||
@ -133,36 +134,6 @@ public final class TwidereLinkify implements Constants {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public SpannableString applyUserProfileLink(@Nullable final CharSequence text,
|
|
||||||
@Nullable final UserKey accountKey, final long extraId, final long userId,
|
|
||||||
final String screenName) {
|
|
||||||
return applyUserProfileLink(text, accountKey, extraId, userId, screenName, mHighlightOption);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SpannableString applyUserProfileLink(@Nullable final CharSequence text,
|
|
||||||
@Nullable final UserKey accountKey, final long extraId, final long userId,
|
|
||||||
final String screenName, final int highlightOption) {
|
|
||||||
return applyUserProfileLink(text, accountKey, extraId, userId, screenName, highlightOption, mOnLinkClickListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public final SpannableString applyUserProfileLink(final CharSequence text,
|
|
||||||
@Nullable final UserKey accountKey, final long extraId, final long userId,
|
|
||||||
final String screenName, final int highlightOption, final OnLinkClickListener listener) {
|
|
||||||
final SpannableString string = SpannableString.valueOf(text);
|
|
||||||
final URLSpan[] spans = string.getSpans(0, string.length(), URLSpan.class);
|
|
||||||
for (final URLSpan span : spans) {
|
|
||||||
string.removeSpan(span);
|
|
||||||
}
|
|
||||||
if (userId > 0) {
|
|
||||||
applyLink(String.valueOf(userId), null, 0, string.length(), string, accountKey, extraId,
|
|
||||||
LINK_TYPE_USER_ID, false, highlightOption, listener);
|
|
||||||
} else if (screenName != null) {
|
|
||||||
applyLink(screenName, null, 0, string.length(), string, accountKey, extraId,
|
|
||||||
LINK_TYPE_MENTION, false, highlightOption, listener);
|
|
||||||
}
|
|
||||||
return string;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHighlightOption(@HighlightStyle final int style) {
|
public void setHighlightOption(@HighlightStyle final int style) {
|
||||||
mHighlightOption = style;
|
mHighlightOption = style;
|
||||||
}
|
}
|
||||||
@ -218,7 +189,10 @@ public final class TwidereLinkify implements Constants {
|
|||||||
}
|
}
|
||||||
string.removeSpan(span);
|
string.removeSpan(span);
|
||||||
String url = span.getURL();
|
String url = span.getURL();
|
||||||
if (accountKey != null && USER_TYPE_FANFOU_COM.equals(accountKey.getHost())) {
|
int linkType = type;
|
||||||
|
if (span instanceof AcctMentionSpan) {
|
||||||
|
linkType = LINK_TYPE_USER_ACCT;
|
||||||
|
} else if (accountKey != null && USER_TYPE_FANFOU_COM.equals(accountKey.getHost())) {
|
||||||
// Fix search path
|
// Fix search path
|
||||||
if (url.startsWith("/")) {
|
if (url.startsWith("/")) {
|
||||||
url = "http://fanfou.com" + url;
|
url = "http://fanfou.com" + url;
|
||||||
@ -236,8 +210,8 @@ public final class TwidereLinkify implements Constants {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
applyLink(url, String.valueOf(string.subSequence(start, end)), start, end,
|
applyLink(url, String.valueOf(string.subSequence(start, end)), start, end,
|
||||||
string, accountKey, extraId, LINK_TYPE_ENTITY_URL, sensitive,
|
string, accountKey, extraId, linkType, sensitive, highlightOption,
|
||||||
highlightOption, listener);
|
listener);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -33,3 +33,11 @@ inline fun <T, reified R> Array<T>.mapIndexedToArray(transform: (Int, T) -> R):
|
|||||||
inline fun <reified R> LongArray.mapToArray(transform: (Long) -> R): Array<R> {
|
inline fun <reified R> LongArray.mapToArray(transform: (Long) -> R): Array<R> {
|
||||||
return Array(size) { transform(this[it]) }
|
return Array(size) { transform(this[it]) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun CharArray.indexOf(element: Char, start: Int, len: Int): Int {
|
||||||
|
@Suppress("LoopToCallChain")
|
||||||
|
for (i in rangeOfSize(start, len)) {
|
||||||
|
if (this[i] == element) return i
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* 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.extension
|
||||||
|
|
||||||
|
import android.text.Spanned
|
||||||
|
import android.text.style.URLSpan
|
||||||
|
import org.mariotaku.twidere.model.SpanItem
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by mariotaku on 2017/4/26.
|
||||||
|
*/
|
||||||
|
|
||||||
|
fun URLSpan.toSpanItem(spanned: Spanned): SpanItem {
|
||||||
|
val spanItem = SpanItem()
|
||||||
|
spanItem.link = url
|
||||||
|
spanItem.type = SpanItem.SpanType.LINK
|
||||||
|
spanItem.start = spanned.getSpanStart(this)
|
||||||
|
spanItem.end = spanned.getSpanEnd(this)
|
||||||
|
return spanItem
|
||||||
|
}
|
@ -23,6 +23,7 @@ import android.text.Spannable
|
|||||||
import android.text.Spanned
|
import android.text.Spanned
|
||||||
import android.text.style.URLSpan
|
import android.text.style.URLSpan
|
||||||
import org.mariotaku.twidere.model.SpanItem
|
import org.mariotaku.twidere.model.SpanItem
|
||||||
|
import org.mariotaku.twidere.text.AcctMentionSpan
|
||||||
import org.mariotaku.twidere.text.ZeroWidthSpan
|
import org.mariotaku.twidere.text.ZeroWidthSpan
|
||||||
|
|
||||||
val SpanItem.length: Int get() = end - start
|
val SpanItem.length: Int get() = end - start
|
||||||
@ -34,6 +35,10 @@ fun Array<SpanItem>.applyTo(spannable: Spannable) {
|
|||||||
spannable.setSpan(ZeroWidthSpan(), span.start, span.end,
|
spannable.setSpan(ZeroWidthSpan(), span.start, span.end,
|
||||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
}
|
}
|
||||||
|
SpanItem.SpanType.ACCT_MENTION -> {
|
||||||
|
spannable.setSpan(AcctMentionSpan(span.link), span.start, span.end,
|
||||||
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
|
}
|
||||||
else -> {
|
else -> {
|
||||||
spannable.setSpan(URLSpan(span.link), span.start, span.end,
|
spannable.setSpan(URLSpan(span.link), span.start, span.end,
|
||||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
|
@ -25,10 +25,10 @@ import org.mariotaku.twidere.model.UserKey
|
|||||||
* Created by mariotaku on 2017/4/19.
|
* Created by mariotaku on 2017/4/19.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
private const val mastodonPlaceholderId = "#mastodon*placeholder#"
|
private const val acctPlaceholderId = "#acct*placeholder#"
|
||||||
|
|
||||||
val UserKey.isMastodonPlaceholder get() = mastodonPlaceholderId == id && host != null
|
val UserKey.isAcctPlaceholder get() = acctPlaceholderId == id && host != null
|
||||||
|
|
||||||
fun MastodonPlaceholderUserKey(host: String?): UserKey {
|
fun AcctPlaceholderUserKey(host: String?): UserKey {
|
||||||
return UserKey(mastodonPlaceholderId, host)
|
return UserKey(acctPlaceholderId, host)
|
||||||
}
|
}
|
@ -24,12 +24,14 @@ import android.text.style.URLSpan
|
|||||||
import org.mariotaku.ktextension.mapToArray
|
import org.mariotaku.ktextension.mapToArray
|
||||||
import org.mariotaku.microblog.library.twitter.model.Status
|
import org.mariotaku.microblog.library.twitter.model.Status
|
||||||
import org.mariotaku.twidere.extension.model.toParcelable
|
import org.mariotaku.twidere.extension.model.toParcelable
|
||||||
|
import org.mariotaku.twidere.extension.toSpanItem
|
||||||
import org.mariotaku.twidere.model.*
|
import org.mariotaku.twidere.model.*
|
||||||
import org.mariotaku.twidere.model.util.ParcelableLocationUtils
|
import org.mariotaku.twidere.model.util.ParcelableLocationUtils
|
||||||
import org.mariotaku.twidere.model.util.ParcelableMediaUtils
|
import org.mariotaku.twidere.model.util.ParcelableMediaUtils
|
||||||
import org.mariotaku.twidere.model.util.ParcelableStatusUtils.addFilterFlag
|
import org.mariotaku.twidere.model.util.ParcelableStatusUtils.addFilterFlag
|
||||||
import org.mariotaku.twidere.model.util.ParcelableUserMentionUtils
|
import org.mariotaku.twidere.model.util.ParcelableUserMentionUtils
|
||||||
import org.mariotaku.twidere.model.util.UserKeyUtils
|
import org.mariotaku.twidere.model.util.UserKeyUtils
|
||||||
|
import org.mariotaku.twidere.text.AcctMentionSpan
|
||||||
import org.mariotaku.twidere.util.HtmlSpanBuilder
|
import org.mariotaku.twidere.util.HtmlSpanBuilder
|
||||||
import org.mariotaku.twidere.util.InternalTwitterContentUtils
|
import org.mariotaku.twidere.util.InternalTwitterContentUtils
|
||||||
|
|
||||||
@ -183,7 +185,13 @@ fun Status.toParcelable(accountKey: UserKey, accountType: String, profileImageSi
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal inline val CharSequence.spanItems get() = (this as? Spanned)?.let { text ->
|
internal inline val CharSequence.spanItems get() = (this as? Spanned)?.let { text ->
|
||||||
text.getSpans(0, length, URLSpan::class.java).mapToArray { SpanItem.from(text, it) }
|
text.getSpans(0, length, URLSpan::class.java).mapToArray {
|
||||||
|
val item = it.toSpanItem(text)
|
||||||
|
if (it is AcctMentionSpan) {
|
||||||
|
item.type = SpanItem.SpanType.ACCT_MENTION
|
||||||
|
}
|
||||||
|
return@mapToArray item
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal inline val String.isHtml get() = contains('<') && contains('>')
|
internal inline val String.isHtml get() = contains('<') && contains('>')
|
||||||
|
@ -28,6 +28,7 @@ import org.mariotaku.twidere.model.ParcelableUser
|
|||||||
import org.mariotaku.twidere.model.UserKey
|
import org.mariotaku.twidere.model.UserKey
|
||||||
import org.mariotaku.twidere.util.HtmlEscapeHelper
|
import org.mariotaku.twidere.util.HtmlEscapeHelper
|
||||||
import org.mariotaku.twidere.util.HtmlSpanBuilder
|
import org.mariotaku.twidere.util.HtmlSpanBuilder
|
||||||
|
import org.mariotaku.twidere.util.emoji.EmojioneTranslator
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by mariotaku on 2017/4/18.
|
* Created by mariotaku on 2017/4/18.
|
||||||
@ -49,7 +50,7 @@ fun Account.toParcelable(accountKey: UserKey, position: Long = 0): ParcelableUse
|
|||||||
obj.name = name
|
obj.name = name
|
||||||
obj.screen_name = username
|
obj.screen_name = username
|
||||||
if (note?.isHtml ?: false) {
|
if (note?.isHtml ?: false) {
|
||||||
val descriptionHtml = HtmlSpanBuilder.fromHtml(note, note)
|
val descriptionHtml = HtmlSpanBuilder.fromHtml(note, note, MastodonSpanProcessor)
|
||||||
obj.description_unescaped = descriptionHtml?.toString()
|
obj.description_unescaped = descriptionHtml?.toString()
|
||||||
obj.description_plain = obj.description_unescaped
|
obj.description_plain = obj.description_unescaped
|
||||||
obj.description_spans = descriptionHtml?.spanItems
|
obj.description_spans = descriptionHtml?.spanItems
|
||||||
@ -72,6 +73,7 @@ fun Account.toParcelable(accountKey: UserKey, position: Long = 0): ParcelableUse
|
|||||||
|
|
||||||
inline val Account.host: String? get() = acct?.let(UserKey::valueOf)?.host
|
inline val Account.host: String? get() = acct?.let(UserKey::valueOf)?.host
|
||||||
|
|
||||||
inline val Account.name: String? get() = displayName?.takeIf(String::isNotEmpty) ?: username
|
inline val Account.name: String? get() = displayName?.takeIf(String::isNotEmpty)
|
||||||
|
?.let(EmojioneTranslator::translate) ?: username
|
||||||
|
|
||||||
fun Account.getKey(host: String?) = UserKey(id, acct?.let(UserKey::valueOf)?.host ?: host)
|
fun Account.getKey(host: String?) = UserKey(id, acct?.let(UserKey::valueOf)?.host ?: host)
|
||||||
|
@ -19,12 +19,18 @@
|
|||||||
|
|
||||||
package org.mariotaku.twidere.extension.model.api.mastodon
|
package org.mariotaku.twidere.extension.model.api.mastodon
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.Spanned
|
||||||
import org.mariotaku.ktextension.mapToArray
|
import org.mariotaku.ktextension.mapToArray
|
||||||
import org.mariotaku.microblog.library.mastodon.model.Status
|
import org.mariotaku.microblog.library.mastodon.model.Status
|
||||||
import org.mariotaku.twidere.extension.model.api.spanItems
|
import org.mariotaku.twidere.extension.model.api.spanItems
|
||||||
import org.mariotaku.twidere.model.*
|
import org.mariotaku.twidere.model.*
|
||||||
import org.mariotaku.twidere.model.util.ParcelableStatusUtils.addFilterFlag
|
import org.mariotaku.twidere.model.util.ParcelableStatusUtils.addFilterFlag
|
||||||
|
import org.mariotaku.twidere.text.AcctMentionSpan
|
||||||
|
import org.mariotaku.twidere.util.HtmlEscapeHelper
|
||||||
import org.mariotaku.twidere.util.HtmlSpanBuilder
|
import org.mariotaku.twidere.util.HtmlSpanBuilder
|
||||||
|
import org.mariotaku.twidere.util.emoji.EmojioneTranslator
|
||||||
|
|
||||||
fun Status.toParcelable(details: AccountDetails): ParcelableStatus {
|
fun Status.toParcelable(details: AccountDetails): ParcelableStatus {
|
||||||
return toParcelable(details.key).apply {
|
return toParcelable(details.key).apply {
|
||||||
@ -40,7 +46,7 @@ fun Status.toParcelable(accountKey: UserKey): ParcelableStatus {
|
|||||||
result.sort_id = sortId
|
result.sort_id = sortId
|
||||||
result.timestamp = createdAt?.time ?: 0
|
result.timestamp = createdAt?.time ?: 0
|
||||||
|
|
||||||
extras.summary_text = spoilerText
|
extras.summary_text = spoilerText?.let(EmojioneTranslator::translate)
|
||||||
extras.visibility = visibility
|
extras.visibility = visibility
|
||||||
extras.external_url = url
|
extras.external_url = url
|
||||||
|
|
||||||
@ -83,9 +89,8 @@ fun Status.toParcelable(accountKey: UserKey): ParcelableStatus {
|
|||||||
result.user_screen_name = account.username
|
result.user_screen_name = account.username
|
||||||
result.user_profile_image_url = account.avatar
|
result.user_profile_image_url = account.avatar
|
||||||
result.user_is_protected = account.isLocked
|
result.user_is_protected = account.isLocked
|
||||||
// Twitter will escape <> to <>, so if a status contains those symbols unescaped
|
// Mastodon has HTML formatted content text
|
||||||
// We should treat this as an html
|
val html = HtmlSpanBuilder.fromHtml(status.content, status.content, MastodonSpanProcessor)
|
||||||
val html = HtmlSpanBuilder.fromHtml(status.content, status.content)
|
|
||||||
result.text_unescaped = html?.toString()
|
result.text_unescaped = html?.toString()
|
||||||
result.text_plain = result.text_unescaped
|
result.text_plain = result.text_unescaped
|
||||||
result.spans = html?.spanItems
|
result.spans = html?.spanItems
|
||||||
@ -106,3 +111,24 @@ private fun calculateDisplayTextRange(spans: Array<SpanItem>?, media: Array<Parc
|
|||||||
val lastMatch = spans.lastOrNull { span -> media.any { span.link == it.page_url } } ?: return null
|
val lastMatch = spans.lastOrNull { span -> media.any { span.link == it.page_url } } ?: return null
|
||||||
return intArrayOf(0, lastMatch.start)
|
return intArrayOf(0, lastMatch.start)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object MastodonSpanProcessor : HtmlSpanBuilder.SpanProcessor {
|
||||||
|
|
||||||
|
override fun appendText(text: Editable, buffer: CharArray, start: Int, len: Int): 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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -639,7 +639,7 @@ class UserFragment : BaseFragment(), OnClickListener, OnLinkClickListener,
|
|||||||
val accountKey = data.getParcelableExtra<UserKey>(EXTRA_ACCOUNT_KEY)
|
val accountKey = data.getParcelableExtra<UserKey>(EXTRA_ACCOUNT_KEY)
|
||||||
var userKey = user.key
|
var userKey = user.key
|
||||||
if (account?.type == AccountType.MASTODON && account?.key?.host != accountKey.host) {
|
if (account?.type == AccountType.MASTODON && account?.key?.host != accountKey.host) {
|
||||||
userKey = MastodonPlaceholderUserKey(user.key.host)
|
userKey = AcctPlaceholderUserKey(user.key.host)
|
||||||
}
|
}
|
||||||
@Referral
|
@Referral
|
||||||
val referral = arguments.getString(EXTRA_REFERRAL)
|
val referral = arguments.getString(EXTRA_REFERRAL)
|
||||||
|
@ -41,7 +41,7 @@ import org.mariotaku.twidere.annotation.Referral
|
|||||||
import org.mariotaku.twidere.extension.api.tryShowUser
|
import org.mariotaku.twidere.extension.api.tryShowUser
|
||||||
import org.mariotaku.twidere.extension.model.api.mastodon.toParcelable
|
import org.mariotaku.twidere.extension.model.api.mastodon.toParcelable
|
||||||
import org.mariotaku.twidere.extension.model.api.toParcelable
|
import org.mariotaku.twidere.extension.model.api.toParcelable
|
||||||
import org.mariotaku.twidere.extension.model.isMastodonPlaceholder
|
import org.mariotaku.twidere.extension.model.isAcctPlaceholder
|
||||||
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
|
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
|
||||||
import org.mariotaku.twidere.model.AccountDetails
|
import org.mariotaku.twidere.model.AccountDetails
|
||||||
import org.mariotaku.twidere.model.ParcelableUser
|
import org.mariotaku.twidere.model.ParcelableUser
|
||||||
@ -162,7 +162,7 @@ class ParcelableUserLoader(
|
|||||||
private fun showMastodonUser(details: AccountDetails): ParcelableUser {
|
private fun showMastodonUser(details: AccountDetails): ParcelableUser {
|
||||||
val mastodon = details.newMicroBlogInstance(context, Mastodon::class.java)
|
val mastodon = details.newMicroBlogInstance(context, Mastodon::class.java)
|
||||||
if (userKey == null) throw MicroBlogException("Invalid user id")
|
if (userKey == null) throw MicroBlogException("Invalid user id")
|
||||||
if (!userKey.isMastodonPlaceholder) {
|
if (!userKey.isAcctPlaceholder) {
|
||||||
return mastodon.getAccount(userKey.id).toParcelable(details)
|
return mastodon.getAccount(userKey.id).toParcelable(details)
|
||||||
}
|
}
|
||||||
if (screenName == null) throw MicroBlogException("Screen name required")
|
if (screenName == null) throw MicroBlogException("Screen name required")
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
import org.mariotaku.twidere.model.UserKey
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by mariotaku on 2017/4/26.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class AcctMentionSpan(acct: String) : URLSpan(acct) {
|
||||||
|
constructor(screenName: String, host: String?) : this(UserKey(screenName, host).toString())
|
||||||
|
}
|
@ -20,6 +20,7 @@
|
|||||||
package org.mariotaku.twidere.util
|
package org.mariotaku.twidere.util
|
||||||
|
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
|
import android.text.Editable
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
import android.text.SpannableStringBuilder
|
import android.text.SpannableStringBuilder
|
||||||
import android.text.Spanned
|
import android.text.Spanned
|
||||||
@ -39,8 +40,8 @@ object HtmlSpanBuilder {
|
|||||||
private val PARSER = SimpleMarkupParser(ParseConfiguration.htmlConfiguration())
|
private val PARSER = SimpleMarkupParser(ParseConfiguration.htmlConfiguration())
|
||||||
|
|
||||||
@Throws(HtmlParseException::class)
|
@Throws(HtmlParseException::class)
|
||||||
fun fromHtml(html: String): Spannable {
|
fun fromHtml(html: String, processor: SpanProcessor? = null): Spannable {
|
||||||
val handler = HtmlSpanHandler()
|
val handler = HtmlSpanHandler(processor)
|
||||||
try {
|
try {
|
||||||
PARSER.parse(html, handler)
|
PARSER.parse(html, handler)
|
||||||
} catch (e: ParseException) {
|
} catch (e: ParseException) {
|
||||||
@ -50,17 +51,19 @@ object HtmlSpanBuilder {
|
|||||||
return handler.text
|
return handler.text
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fromHtml(html: String?, fallback: CharSequence?): CharSequence? {
|
fun fromHtml(html: String?, fallback: CharSequence?, processor: SpanProcessor? = null): CharSequence? {
|
||||||
if (html == null) return fallback
|
if (html == null) return fallback
|
||||||
try {
|
try {
|
||||||
return fromHtml(html)
|
return fromHtml(html, processor)
|
||||||
} catch (e: HtmlParseException) {
|
} catch (e: HtmlParseException) {
|
||||||
return fallback
|
return fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun applyTag(sb: SpannableStringBuilder, start: Int, end: Int, info: TagInfo) {
|
private fun applyTag(sb: SpannableStringBuilder, start: Int, end: Int, info: TagInfo,
|
||||||
|
processor: SpanProcessor?) {
|
||||||
|
if (processor?.applySpan(sb, start, end, info) ?: false) return
|
||||||
if (info.nameLower == "br") {
|
if (info.nameLower == "br") {
|
||||||
sb.append('\n')
|
sb.append('\n')
|
||||||
} else {
|
} else {
|
||||||
@ -88,26 +91,50 @@ object HtmlSpanBuilder {
|
|||||||
return info.indexOfLast { it.name.equals(name, ignoreCase = true) }
|
return info.indexOfLast { it.name.equals(name, ignoreCase = true) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private class HtmlParseException : RuntimeException {
|
interface SpanProcessor {
|
||||||
internal constructor() : super() {}
|
|
||||||
|
|
||||||
internal constructor(detailMessage: String) : super(detailMessage) {}
|
/**
|
||||||
|
* @param text Text before content in [buffer] appended
|
||||||
|
* @param buffer Raw html buffer
|
||||||
|
* @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
|
||||||
|
|
||||||
internal constructor(detailMessage: String, throwable: Throwable) : super(detailMessage, throwable) {}
|
/**
|
||||||
|
* @param text Text to apply span from [info]
|
||||||
|
* @param start Start index for applying span
|
||||||
|
* @param end End index for applying span
|
||||||
|
* @param info Tag info
|
||||||
|
*/
|
||||||
|
fun applySpan(text: Editable, start: Int, end: Int, info: TagInfo): Boolean = false
|
||||||
|
|
||||||
internal constructor(throwable: Throwable) : super(throwable) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private 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 nameLower = name.toLowerCase(Locale.US)
|
val nameLower = name.toLowerCase(Locale.US)
|
||||||
|
|
||||||
internal fun getAttribute(attr: String): String? {
|
fun getAttribute(attr: String): String? {
|
||||||
return attributes?.get(attr)
|
return attributes?.get(attr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class HtmlSpanHandler internal constructor() : AbstractSimpleMarkupHandler() {
|
private class HtmlParseException : RuntimeException {
|
||||||
|
|
||||||
|
internal constructor() : super()
|
||||||
|
|
||||||
|
internal constructor(detailMessage: String) : super(detailMessage)
|
||||||
|
internal constructor(detailMessage: String, throwable: Throwable) : super(detailMessage, throwable)
|
||||||
|
|
||||||
|
|
||||||
|
internal constructor(throwable: Throwable) : super(throwable)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class HtmlSpanHandler(
|
||||||
|
val processor: SpanProcessor?
|
||||||
|
) : AbstractSimpleMarkupHandler() {
|
||||||
|
|
||||||
private val sb = SpannableStringBuilder()
|
private val sb = SpannableStringBuilder()
|
||||||
private var tagInfo = ArrayList<TagInfo>()
|
private var tagInfo = ArrayList<TagInfo>()
|
||||||
@ -122,7 +149,9 @@ object HtmlSpanBuilder {
|
|||||||
if (buffer[lineBreakIndex] == '\n') break
|
if (buffer[lineBreakIndex] == '\n') break
|
||||||
lineBreakIndex++
|
lineBreakIndex++
|
||||||
}
|
}
|
||||||
sb.append(HtmlEscapeHelper.unescape(String(buffer, cur, lineBreakIndex - cur)))
|
if (!(processor?.appendText(sb, buffer, cur, lineBreakIndex - cur) ?: false)) {
|
||||||
|
sb.append(HtmlEscapeHelper.unescape(String(buffer, cur, lineBreakIndex - cur)))
|
||||||
|
}
|
||||||
cur = lineBreakIndex + 1
|
cur = lineBreakIndex + 1
|
||||||
}
|
}
|
||||||
lastTag = null
|
lastTag = null
|
||||||
@ -132,7 +161,7 @@ object HtmlSpanBuilder {
|
|||||||
val lastIndex = lastIndexOfTag(tagInfo, elementName)
|
val lastIndex = lastIndexOfTag(tagInfo, elementName)
|
||||||
if (lastIndex == -1) return
|
if (lastIndex == -1) return
|
||||||
val info = tagInfo[lastIndex]
|
val info = tagInfo[lastIndex]
|
||||||
applyTag(sb, info.start, sb.length, info)
|
applyTag(sb, info.start, sb.length, info, processor)
|
||||||
tagInfo.removeAt(lastIndex)
|
tagInfo.removeAt(lastIndex)
|
||||||
lastTag = info
|
lastTag = info
|
||||||
}
|
}
|
||||||
@ -153,11 +182,12 @@ object HtmlSpanBuilder {
|
|||||||
minimized: Boolean, line: Int, col: Int) {
|
minimized: Boolean, line: Int, col: Int) {
|
||||||
if (minimized) {
|
if (minimized) {
|
||||||
val info = TagInfo(sb.length, elementName, attributes)
|
val info = TagInfo(sb.length, elementName, attributes)
|
||||||
applyTag(sb, info.start, sb.length, info)
|
applyTag(sb, info.start, sb.length, info, processor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val text: Spannable
|
val text: Spannable
|
||||||
get() = sb
|
get() = sb
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ import org.mariotaku.twidere.app.TwidereApplication
|
|||||||
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_ACCOUNT_KEY
|
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_ACCOUNT_KEY
|
||||||
import org.mariotaku.twidere.constant.displaySensitiveContentsKey
|
import org.mariotaku.twidere.constant.displaySensitiveContentsKey
|
||||||
import org.mariotaku.twidere.constant.newDocumentApiKey
|
import org.mariotaku.twidere.constant.newDocumentApiKey
|
||||||
|
import org.mariotaku.twidere.extension.model.AcctPlaceholderUserKey
|
||||||
import org.mariotaku.twidere.model.UserKey
|
import org.mariotaku.twidere.model.UserKey
|
||||||
import org.mariotaku.twidere.model.util.ParcelableMediaUtils
|
import org.mariotaku.twidere.model.util.ParcelableMediaUtils
|
||||||
import org.mariotaku.twidere.util.TwidereLinkify.OnLinkClickListener
|
import org.mariotaku.twidere.util.TwidereLinkify.OnLinkClickListener
|
||||||
@ -108,6 +109,12 @@ open class OnLinkClickHandler(
|
|||||||
preferences[newDocumentApiKey], Referral.USER_MENTION, null)
|
preferences[newDocumentApiKey], Referral.USER_MENTION, null)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
TwidereLinkify.LINK_TYPE_USER_ACCT -> {
|
||||||
|
val acctKey = UserKey.valueOf(link)
|
||||||
|
IntentUtils.openUserProfile(context, accountKey, AcctPlaceholderUserKey(acctKey.host),
|
||||||
|
acctKey.id, null, preferences[newDocumentApiKey], Referral.USER_MENTION, null)
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* 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.util.emoji
|
||||||
|
|
||||||
|
import org.mariotaku.commons.emojione.ShortnameToUnicodeTranslator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by mariotaku on 2017/4/26.
|
||||||
|
*/
|
||||||
|
object EmojioneTranslator: ShortnameToUnicodeTranslator()
|
Loading…
x
Reference in New Issue
Block a user