improved emoji support

This commit is contained in:
Mariotaku Lee 2017-05-13 14:19:23 +08:00
parent b2cf3bd2cb
commit c59fc51434
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
55 changed files with 540 additions and 605 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
*.bat text eol=crlf

View File

@ -1,80 +0,0 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2015 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.preference;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.text.TextUtils;
import android.util.AttributeSet;
import java.util.List;
public abstract class ComponentPickerPreference extends ThemedListPreference {
protected final PackageManager packageManager;
public ComponentPickerPreference(final Context context) {
this(context, null);
}
public ComponentPickerPreference(final Context context, final AttributeSet attrs) {
super(context, attrs);
packageManager = context.getPackageManager();
init();
}
@Override
public CharSequence getSummary() {
if (isNoneValue(getValue())) return getNoneEntry();
return super.getSummary();
}
protected abstract String getIntentAction();
protected abstract String getNoneEntry();
private void init() {
final Intent queryIntent = new Intent(getIntentAction());
final List<ResolveInfo> infoList = resolve(queryIntent);
final int infoListSize = infoList.size();
final CharSequence[] entries = new CharSequence[infoListSize + 1], values = new CharSequence[infoListSize + 1];
entries[0] = getNoneEntry();
values[0] = "";
for (int i = 0; i < infoListSize; i++) {
final ResolveInfo info = infoList.get(i);
entries[i + 1] = info.loadLabel(packageManager);
values[i + 1] = getComponentName(info).flattenToString();
}
setEntries(entries);
setEntryValues(values);
}
protected abstract ComponentName getComponentName(ResolveInfo info);
protected abstract List<ResolveInfo> resolve(Intent queryIntent);
public static boolean isNoneValue(final String value) {
return TextUtils.isEmpty(value) || "none".equals(value);
}
}

View File

@ -1,49 +0,0 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2015 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.preference;
import android.content.Context;
import android.util.AttributeSet;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.constant.IntentConstants;
/**
* Created by mariotaku on 15/12/22.
*/
public class EmojiSupportPreference extends ActivityPickerPreference {
public EmojiSupportPreference(Context context) {
super(context);
}
public EmojiSupportPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected String getIntentAction() {
return IntentConstants.INTENT_ACTION_EMOJI_SUPPORT_ABOUT;
}
@Override
protected String getNoneEntry() {
return getContext().getString(R.string.system_default);
}
}

View File

@ -1,37 +0,0 @@
package org.mariotaku.twidere.preference;
import android.content.Context;
import android.support.v7.preference.ListPreference;
import android.support.v7.preference.PreferenceFragmentCompat;
import android.util.AttributeSet;
import org.mariotaku.twidere.fragment.ThemedListPreferenceDialogFragmentCompat;
import org.mariotaku.twidere.preference.iface.IDialogPreference;
/**
* Created by mariotaku on 16/3/15.
*/
public class ThemedListPreference extends ListPreference implements IDialogPreference {
public ThemedListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public ThemedListPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ThemedListPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ThemedListPreference(Context context) {
super(context);
}
@Override
public void displayDialog(PreferenceFragmentCompat fragment) {
ThemedListPreferenceDialogFragmentCompat df = ThemedListPreferenceDialogFragmentCompat.newInstance(getKey());
df.setTargetFragment(fragment, 0);
df.show(fragment.getFragmentManager(), getKey());
}
}

View File

@ -4,16 +4,22 @@ import android.graphics.Typeface
import android.view.View
import android.widget.TextView
val TextView.empty: Boolean
inline val TextView.empty: Boolean
get() = length() <= 0
var TextView.string: String?
inline var TextView.string: String?
get() = text?.toString()
set(value) {
text = value
}
var TextView.charSequence: CharSequence?
inline var TextView.spannable: CharSequence?
get() = text
set(value) {
setText(value, TextView.BufferType.SPANNABLE)
}
inline var TextView.charSequence: CharSequence?
get() = text
set(value) {
text = value
@ -25,9 +31,9 @@ fun TextView.applyFontFamily(lightFont: Boolean) {
}
}
fun TextView.hideIfEmpty() {
fun TextView.hideIfEmpty(hideVisibility: Int = View.GONE) {
visibility = if (empty) {
View.GONE
hideVisibility
} else {
View.VISIBLE
}

View File

@ -89,7 +89,7 @@ import org.mariotaku.twidere.model.draft.UpdateStatusActionExtras
import org.mariotaku.twidere.model.schedule.ScheduleInfo
import org.mariotaku.twidere.model.util.AccountUtils
import org.mariotaku.twidere.model.util.ParcelableLocationUtils
import org.mariotaku.twidere.preference.ServicePickerPreference
import org.mariotaku.twidere.preference.ComponentPickerPreference
import org.mariotaku.twidere.provider.TwidereDataStore.Drafts
import org.mariotaku.twidere.service.LengthyOperationsService
import org.mariotaku.twidere.task.compose.AbsAddMediaTask
@ -250,7 +250,7 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
accountSelectorButton.setOnClickListener(this)
replyLabel.setOnClickListener(this)
hintLabel.text = HtmlSpanBuilder.fromHtml(getString(R.string.hint_status_reply_to_user_removed)).apply {
hintLabel.spannable = HtmlSpanBuilder.fromHtml(getString(R.string.hint_status_reply_to_user_removed)).apply {
val dialogSpan = getSpans(0, length, URLSpan::class.java).firstOrNull {
"#dialog" == it.url
}
@ -357,8 +357,8 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
override fun onStart() {
super.onStart()
imageUploaderUsed = !ServicePickerPreference.isNoneValue(kPreferences[mediaUploaderKey])
statusShortenerUsed = !ServicePickerPreference.isNoneValue(kPreferences[statusShortenerKey])
imageUploaderUsed = !ComponentPickerPreference.isNoneValue(kPreferences[mediaUploaderKey])
statusShortenerUsed = !ComponentPickerPreference.isNoneValue(kPreferences[statusShortenerKey])
if (kPreferences[attachLocationKey]) {
if (checkAnySelfPermissionsGranted(AndroidPermission.ACCESS_COARSE_LOCATION,
AndroidPermission.ACCESS_FINE_LOCATION)) {
@ -1162,7 +1162,7 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
return false
}
val replyToName = userColorNameManager.getDisplayName(status, nameFirst)
replyLabel.text = getString(R.string.label_quote_name_text, replyToName, status.text_unescaped)
replyLabel.spannable = getString(R.string.label_quote_name_text, replyToName, status.text_unescaped)
replyLabel.visibility = View.VISIBLE
editText.hint = getString(R.string.label_quote_name, replyToName)
return true
@ -1174,7 +1174,7 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
return false
}
val replyToName = userColorNameManager.getDisplayName(status, nameFirst)
replyLabel.text = getString(R.string.label_reply_name_text, replyToName, status.text_unescaped)
replyLabel.spannable = getString(R.string.label_reply_name_text, replyToName, status.text_unescaped)
replyLabel.visibility = View.VISIBLE
editText.hint = getString(R.string.label_reply_name, replyToName)
return true
@ -1362,7 +1362,7 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
if (location != null) {
val attachPreciseLocation = kPreferences[attachPreciseLocationKey]
if (attachPreciseLocation) {
locationLabel.text = ParcelableLocationUtils.getHumanReadableString(location, 3)
locationLabel.spannable = ParcelableLocationUtils.getHumanReadableString(location, 3)
} else {
if (locationLabel.tag == null || location != recentLocation) {
val task = DisplayPlaceNameTask()
@ -2086,12 +2086,12 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
val attachPreciseLocation = preferences[attachPreciseLocationKey]
if (attachLocation) {
if (attachPreciseLocation) {
textView.text = ParcelableLocationUtils.getHumanReadableString(location, 3)
textView.spannable = ParcelableLocationUtils.getHumanReadableString(location, 3)
textView.tag = location
} else {
val tag = textView.tag
if (tag is Address) {
textView.text = tag.locality
textView.spannable = tag.locality
} else if (tag is NoAddress) {
textView.setText(R.string.label_location_your_coarse_location)
} else {
@ -2112,20 +2112,20 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
if (attachLocation) {
if (attachPreciseLocation) {
val location = params
textView.text = ParcelableLocationUtils.getHumanReadableString(location, 3)
textView.spannable = ParcelableLocationUtils.getHumanReadableString(location, 3)
textView.tag = location
} else if (addresses == null || addresses.isEmpty()) {
val tag = textView.tag
if (tag is Address) {
textView.text = tag.locality
textView.spannable = tag.locality
} else {
textView.setText(R.string.label_location_your_coarse_location)
textView.tag = NoAddress()
}
} else {
val address = addresses[0]
textView.spannable = address.locality
textView.tag = address
textView.text = address.locality
}
} else {
textView.setText(R.string.no_location)

View File

@ -47,6 +47,7 @@ import jopt.csp.util.SortableIntList
import kotlinx.android.synthetic.main.activity_quick_search_bar.*
import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.empty
import org.mariotaku.ktextension.spannable
import org.mariotaku.twidere.BuildConfig
import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants.QUERY_PARAM_ACCOUNT_KEY
@ -392,23 +393,23 @@ class QuickSearchBarActivity : BaseActivity(), OnClickListener, LoaderCallbacks<
val holder = view.tag as SearchViewHolder
val title = cursor.getString(indices.title)
holder.edit_query.tag = title
holder.text1.text = title
holder.text1.spannable = title
holder.icon.setImageResource(R.drawable.ic_action_history)
}
VIEW_TYPE_SAVED_SEARCH -> {
val holder = view.tag as SearchViewHolder
val title = cursor.getString(indices.title)
holder.edit_query.tag = title
holder.text1.text = title
holder.text1.spannable = title
holder.icon.setImageResource(R.drawable.ic_action_save)
}
VIEW_TYPE_USER_SUGGESTION_ITEM -> {
val holder = view.tag as UserViewHolder
val userKey = UserKey.valueOf(cursor.getString(indices.extra_id))
holder.text1.text = userColorNameManager.getUserNickname(userKey,
holder.text1.spannable = userColorNameManager.getUserNickname(userKey,
cursor.getString(indices.title))
holder.text2.visibility = View.VISIBLE
holder.text2.text = "@${cursor.getString(indices.summary)}"
holder.text2.spannable = "@${cursor.getString(indices.summary)}"
holder.icon.clearColorFilter()
requestManager.loadProfileImage(context, cursor.getString(indices.icon),
profileImageStyle, cornerRadius = holder.icon.cornerRadius,
@ -417,7 +418,7 @@ class QuickSearchBarActivity : BaseActivity(), OnClickListener, LoaderCallbacks<
}
VIEW_TYPE_USER_SCREEN_NAME -> {
val holder = view.tag as UserViewHolder
holder.text1.text = "@${cursor.getString(indices.title)}"
holder.text1.spannable = "@${cursor.getString(indices.title)}"
holder.text2.visibility = View.GONE
holder.icon.setColorFilter(holder.text1.currentTextColor, Mode.SRC_ATOP)
//TODO cancel image load

View File

@ -19,12 +19,12 @@
package org.mariotaku.twidere.adapter
import android.annotation.SuppressLint
import android.content.Context
import android.view.View
import android.view.ViewGroup
import com.bumptech.glide.RequestManager
import kotlinx.android.synthetic.main.list_item_simple_user.view.*
import org.mariotaku.ktextension.spannable
import org.mariotaku.twidere.R
import org.mariotaku.twidere.extension.loadProfileImage
import org.mariotaku.twidere.model.AccountDetails
@ -61,10 +61,9 @@ class AccountsSpinnerAdapter(
val icon = view.profileImage
if (!item.dummy) {
text1?.visibility = View.VISIBLE
text1?.text = item.user.name
text1?.spannable = item.user.name
text2?.visibility = View.VISIBLE
@SuppressLint("SetTextI18n")
text2?.text = "@${item.user.screen_name}"
text2?.spannable = "@${item.user.screen_name}"
if (icon != null) {
if (profileImageEnabled) {
icon.visibility = View.VISIBLE
@ -76,7 +75,7 @@ class AccountsSpinnerAdapter(
}
} else {
text1?.visibility = View.VISIBLE
text1?.text = dummyItemText
text1?.spannable = dummyItemText
text2?.visibility = View.GONE
icon?.visibility = View.GONE
}

View File

@ -28,6 +28,7 @@ import android.view.View
import android.widget.TextView
import com.bumptech.glide.RequestManager
import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.spannable
import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants.*
import org.mariotaku.twidere.constant.displayProfileImageKey
@ -71,9 +72,9 @@ class ComposeAutoCompleteAdapter(context: Context, val requestManager: RequestMa
icon.style = profileImageStyle
if (Suggestions.AutoComplete.TYPE_USERS == cursor.getString(indices.type)) {
text1.text = userColorNameManager.getUserNickname(cursor.getString(indices.extra_id),
text1.spannable = userColorNameManager.getUserNickname(cursor.getString(indices.extra_id),
cursor.getString(indices.title))
text2.text = String.format("@%s", cursor.getString(indices.summary))
text2.spannable = String.format("@%s", cursor.getString(indices.summary))
if (displayProfileImage) {
val profileImageUrl = cursor.getString(indices.icon)
requestManager.loadProfileImage(context, profileImageUrl, profileImageStyle).into(icon)
@ -83,7 +84,7 @@ class ComposeAutoCompleteAdapter(context: Context, val requestManager: RequestMa
icon.clearColorFilter()
} else {
text1.text = String.format("#%s", cursor.getString(indices.title))
text1.spannable = String.format("#%s", cursor.getString(indices.title))
text2.setText(R.string.hashtag)
icon.setImageResource(R.drawable.ic_action_hashtag)

View File

@ -30,6 +30,7 @@ import android.view.View
import android.widget.TextView
import com.bumptech.glide.RequestManager
import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.spannable
import org.mariotaku.library.objectcursor.ObjectCursor
import org.mariotaku.sqliteqb.library.Columns
import org.mariotaku.sqliteqb.library.Expression
@ -38,7 +39,7 @@ import org.mariotaku.twidere.R
import org.mariotaku.twidere.constant.displayProfileImageKey
import org.mariotaku.twidere.constant.profileImageStyleKey
import org.mariotaku.twidere.extension.loadProfileImage
import org.mariotaku.twidere.model.ParcelableUser
import org.mariotaku.twidere.model.ParcelableLiteUser
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.provider.TwidereDataStore.CachedUsers
import org.mariotaku.twidere.util.UserColorNameManager
@ -61,7 +62,7 @@ class UserAutoCompleteAdapter(
private val displayProfileImage: Boolean
private var profileImageStyle: Int
private var indices: ObjectCursor.CursorIndices<ParcelableUser>? = null
private var indices: ObjectCursor.CursorIndices<ParcelableLiteUser>? = null
var accountKey: UserKey? = null
@ -72,25 +73,22 @@ class UserAutoCompleteAdapter(
}
override fun bindView(view: View, context: Context, cursor: Cursor) {
val indices = this.indices!!
val user = indices!!.newObject(cursor)
val text1 = view.findViewById(android.R.id.text1) as TextView
val text2 = view.findViewById(android.R.id.text2) as TextView
val icon = view.findViewById(android.R.id.icon) as ProfileImageView
icon.style = profileImageStyle
text1.text = userColorNameManager.getUserNickname(cursor.getString(indices[CachedUsers.USER_KEY]), cursor.getString(indices[CachedUsers.NAME]))
@SuppressLint("SetTextI18n")
text2.text = "@${cursor.getString(indices[CachedUsers.SCREEN_NAME])}"
text1.spannable = userColorNameManager.getUserNickname(user.key, user.name)
text2.spannable = "@${user.screen_name}"
if (displayProfileImage) {
val profileImageUrl = cursor.getString(indices[CachedUsers.PROFILE_IMAGE_URL])
requestManager.loadProfileImage(context, profileImageUrl, profileImageStyle).into(icon)
requestManager.loadProfileImage(context, user, profileImageStyle).into(icon)
} else {
//TODO cancel image load
}
icon.visibility = if (displayProfileImage) View.VISIBLE else View.GONE
super.bindView(view, context, cursor)
}
fun closeCursor() {
@ -100,8 +98,8 @@ class UserAutoCompleteAdapter(
}
}
override fun convertToString(cursor: Cursor?): CharSequence {
return cursor!!.getString(indices!![CachedUsers.SCREEN_NAME])
override fun convertToString(cursor: Cursor): CharSequence {
return cursor.getString(indices!![CachedUsers.SCREEN_NAME])
}
override fun runQueryOnBackgroundThread(constraint: CharSequence): Cursor? {
@ -127,7 +125,7 @@ class UserAutoCompleteAdapter(
}
override fun swapCursor(cursor: Cursor?): Cursor? {
indices = cursor?.let { ObjectCursor.indicesFrom(it, ParcelableUser::class.java) }
indices = cursor?.let { ObjectCursor.indicesFrom(it, ParcelableLiteUser::class.java) }
return super.swapCursor(cursor)
}

View File

@ -0,0 +1,155 @@
/*
* 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.graphics.drawable.Drawable
import android.text.Spannable
import android.text.Spanned
import android.widget.TextView
import org.mariotaku.commons.text.CodePointArray
import org.mariotaku.commons.text.get
import org.mariotaku.twidere.text.style.EmojiSpan
import org.mariotaku.twidere.text.util.EmojiEditableFactory
import org.mariotaku.twidere.text.util.EmojiSpannableFactory
import org.mariotaku.twidere.util.ExternalThemeManager
/**
* Created by mariotaku on 2017/5/13.
*/
fun TextView.setupEmojiFactory() {
if (isInEditMode) return
setSpannableFactory(EmojiSpannableFactory(this))
setEditableFactory(EmojiEditableFactory(this))
}
fun ExternalThemeManager.Emoji.applyTo(text: Spannable, textStart: Int = 0, textLength: Int = text.length) {
if (!isSupported) return
val array = CodePointArray(text)
var arrayIdx = array.length() - 1
while (arrayIdx >= 0) {
val codePoint = array[arrayIdx]
if (isEmoji(codePoint)) {
val arrayEnd = arrayIdx + 1
var arrayIdxOffset = 0
val textIdx = array.indexOfText(codePoint, arrayIdx)
var textIdxOffset = 0
var skippedIndex = 0
if (textIdx == -1 || textIdx < textStart) {
arrayIdx--
continue
}
val textEnd = textIdx + Character.charCount(codePoint)
if (arrayIdx > 0) {
val prevCodePoint = array[arrayIdx - 1]
when {
isRegionalIndicatorSymbol(codePoint) -> if (isRegionalIndicatorSymbol(prevCodePoint)) {
arrayIdxOffset = -1
textIdxOffset = -Character.charCount(prevCodePoint)
skippedIndex = -1
}
isModifier(codePoint) -> if (isEmoji(prevCodePoint)) {
arrayIdxOffset = -1
textIdxOffset = -Character.charCount(prevCodePoint)
skippedIndex = -1
}
isKeyCap(codePoint) -> if (isPhoneNumberSymbol(prevCodePoint)) {
arrayIdxOffset = -1
textIdxOffset = -Character.charCount(prevCodePoint)
skippedIndex = -1
}
isZeroWidthJoin(prevCodePoint) -> {
var notValidControlCount = 0
var charCount = 0
for (i in arrayIdx - 1 downTo 0) {
val cp = array.get(i)
charCount += Character.charCount(cp)
if (isZeroWidthJoin(cp) || isVariationSelector(cp)) {
// Ignore
notValidControlCount = 0
continue
}
notValidControlCount++
if (notValidControlCount > 1 || i == 0) {
arrayIdxOffset = i - arrayIdx + 1
textIdxOffset = -charCount + Character.charCount(cp)
skippedIndex = i - arrayIdx + 1
break
}
}
}
}
}
if (textEnd > textStart + textLength) {
arrayIdx--
continue
}
var spans = text.getSpans(textIdx + textIdxOffset, textEnd, EmojiSpan::class.java)
if (spans.isEmpty()) {
var drawable: Drawable? = getEmojiDrawableFor(*array[arrayIdx + arrayIdxOffset..arrayEnd])
if (drawable == null) {
// Not emoji combination, just use fallback
textIdxOffset = 0
arrayIdxOffset = 0
skippedIndex = 0
spans = text.getSpans(textIdx + textIdxOffset, textEnd, EmojiSpan::class.java)
if (spans.isEmpty()) {
drawable = getEmojiDrawableFor(*array[arrayIdx + arrayIdxOffset..arrayEnd])
}
}
if (drawable != null) {
text.setSpan(EmojiSpan(drawable), textIdx + textIdxOffset, textEnd,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
arrayIdx += skippedIndex
}
arrayIdx--
}
}
private fun isVariationSelector(codePoint: Int): Boolean {
return codePoint == 0xfe0f
}
private fun isZeroWidthJoin(codePoint: Int): Boolean {
return codePoint == 0x200d
}
private fun isPhoneNumberSymbol(codePoint: Int): Boolean {
return codePoint == 0x0023 || codePoint == 0x002a || codePoint in 0x0030..0x0039
}
private fun isModifier(codePoint: Int): Boolean {
return codePoint in 0x1f3fb..0x1f3ff
}
private fun isEmoji(codePoint: Int): Boolean {
return !Character.isLetterOrDigit(codePoint)
}
private fun isRegionalIndicatorSymbol(codePoint: Int): Boolean {
return codePoint in 0x1f1e6..0x1f1ff
}
private fun isKeyCap(codePoint: Int): Boolean {
return codePoint == 0x20e3
}

View File

@ -23,6 +23,8 @@ import android.graphics.Rect
import android.graphics.RectF
import android.support.annotation.UiThread
import android.view.View
import android.widget.TextView
import org.mariotaku.ktextension.empty
private val tempLocation = IntArray(2)
private val tempRect = Rect()
@ -70,6 +72,14 @@ fun View.removeSystemUiVisibility(systemUiVisibility: Int) {
this.systemUiVisibility = this.systemUiVisibility and systemUiVisibility.inv()
}
fun View.hideIfEmpty(dependency: TextView, hideVisibility: Int = View.GONE) {
visibility = if (dependency.empty) {
hideVisibility
} else {
View.VISIBLE
}
}
private fun offsetToRoot(view: View, rect: Rect) {
var parent = view.parent as? View
while (parent != null) {

View File

@ -25,6 +25,7 @@ import android.os.Bundle
import android.support.v7.app.AlertDialog
import android.widget.CheckBox
import android.widget.TextView
import org.mariotaku.ktextension.spannable
import org.mariotaku.twidere.R
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_USER
import org.mariotaku.twidere.extension.applyTheme
@ -61,7 +62,7 @@ abstract class AbsUserMuteBlockDialogFragment : BaseDialogFragment(), DialogInte
MessageDialogFragment.show(childFragmentManager, title = getString(R.string.filter_everywhere),
message = getString(R.string.filter_everywhere_description), tag = "filter_everywhere_help")
}
confirmMessageView.text = getMessage(user)
confirmMessageView.spannable = getMessage(user)
}
return dialog
}

View File

@ -55,10 +55,7 @@ import kotlinx.android.synthetic.main.header_drawer_account_selector.view.*
import org.mariotaku.chameleon.Chameleon
import org.mariotaku.kpreferences.get
import org.mariotaku.kpreferences.set
import org.mariotaku.ktextension.addOnAccountsUpdatedListenerSafe
import org.mariotaku.ktextension.removeOnAccountsUpdatedListenerSafe
import org.mariotaku.ktextension.setItemAvailability
import org.mariotaku.ktextension.setMenuItemIcon
import org.mariotaku.ktextension.*
import org.mariotaku.twidere.Constants.EXTRA_FEATURES_NOTICE_VERSION
import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants.*
@ -546,8 +543,8 @@ class AccountsDashboardFragment : BaseFragment(), LoaderCallbacks<AccountsInfo>,
private fun displayCurrentAccount(profileImageSnapshot: Drawable?) {
if (context == null || isDetached || (activity?.isFinishing ?: true)) return
val account = accountsAdapter.selectedAccount ?: return
accountProfileNameView.text = account.user.name
accountProfileScreenNameView.text = "@${account.user.screen_name}"
accountProfileNameView.spannable = account.user.name
accountProfileScreenNameView.spannable = "@${account.user.screen_name}"
Glide.with(this).loadProfileImage(context, account, preferences[profileImageStyleKey],
accountProfileImageView.cornerRadius, accountProfileImageView.cornerRadiusRatio,
ProfileImageSize.REASONABLY_SMALL).placeholder(profileImageSnapshot).into(accountProfileImageView)

View File

@ -81,6 +81,8 @@ open class BaseFragment : Fragment(), IBaseFragment<BaseFragment> {
lateinit var dns: Dns
@Inject
lateinit var syncPreferences: SyncPreferences
@Inject
lateinit var externalThemeManager: ExternalThemeManager
protected val statusScheduleProvider: StatusScheduleProvider?
get() = statusScheduleProviderFactory.newInstance(context)

View File

@ -34,7 +34,6 @@ import android.support.v4.app.LoaderManager.LoaderCallbacks
import android.support.v4.content.CursorLoader
import android.support.v4.content.Loader
import android.support.v7.app.AlertDialog
import android.text.TextUtils
import android.util.SparseArray
import android.view.*
import android.widget.*
@ -48,6 +47,7 @@ import org.mariotaku.chameleon.Chameleon
import org.mariotaku.ktextension.Bundle
import org.mariotaku.ktextension.contains
import org.mariotaku.ktextension.set
import org.mariotaku.ktextension.spannable
import org.mariotaku.library.objectcursor.ObjectCursor
import org.mariotaku.sqliteqb.library.Columns.Column
import org.mariotaku.sqliteqb.library.Expression
@ -439,7 +439,7 @@ class CustomTabsFragment : BaseFragment(), LoaderCallbacks<Cursor?>, MultiChoice
view.findViewById(android.R.id.text2).visibility = View.GONE
val text1 = view.findViewById(android.R.id.text1) as TextView
val item = getItem(position)
text1.text = item.name
text1.spannable = item.name
bindIconView(item, view)
return view
}
@ -484,12 +484,12 @@ class CustomTabsFragment : BaseFragment(), LoaderCallbacks<Cursor?>, MultiChoice
val iconKey = tempTab.icon
if (type != null && CustomTabUtils.isTabTypeValid(type)) {
val typeName = CustomTabUtils.getTabTypeName(context, type)
holder.text1.text = if (TextUtils.isEmpty(name)) typeName else name
holder.text1.spannable = name?.takeIf(String::isNotEmpty) ?: typeName
holder.text1.paintFlags = holder.text1.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
holder.text2.visibility = View.VISIBLE
holder.text2.text = typeName
} else {
holder.text1.text = name
holder.text1.spannable = name
holder.text1.paintFlags = holder.text1.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
holder.text2.setText(R.string.invalid_tab)
}

View File

@ -62,10 +62,7 @@ import kotlinx.android.synthetic.main.header_status.view.*
import kotlinx.android.synthetic.main.layout_content_fragment_common.*
import org.mariotaku.abstask.library.TaskStarter
import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.applyFontFamily
import org.mariotaku.ktextension.contains
import org.mariotaku.ktextension.findPositionByItemId
import org.mariotaku.ktextension.hideIfEmpty
import org.mariotaku.ktextension.*
import org.mariotaku.library.objectcursor.ObjectCursor
import org.mariotaku.microblog.library.MicroBlog
import org.mariotaku.microblog.library.MicroBlogException
@ -424,7 +421,7 @@ class StatusFragment : BaseFragment(), LoaderCallbacks<SingleResponse<Parcelable
adapter.loadMoreSupportedPosition = ILoadMoreSupportAdapter.NONE
setState(STATE_ERROR)
val errorInfo = StatusCodeMessageUtils.getErrorInfo(context, data.exception!!)
errorText.text = errorInfo.message
errorText.spannable = errorInfo.message
errorIcon.setImageResource(errorInfo.icon)
}
activity.supportInvalidateOptionsMenu()
@ -742,10 +739,10 @@ class StatusFragment : BaseFragment(), LoaderCallbacks<SingleResponse<Parcelable
if (status.retweet_id != null) {
val retweetedBy = colorNameManager.getDisplayName(status.retweeted_by_user_key!!,
status.retweeted_by_user_name!!, status.retweeted_by_user_acct!!, nameFirst)
retweetedByView.text = context.getString(R.string.name_retweeted, retweetedBy)
retweetedByView.spannable = context.getString(R.string.name_retweeted, retweetedBy)
retweetedByView.visibility = View.VISIBLE
} else {
retweetedByView.text = null
retweetedByView.spannable = null
retweetedByView.visibility = View.GONE
}
@ -776,16 +773,11 @@ class StatusFragment : BaseFragment(), LoaderCallbacks<SingleResponse<Parcelable
linkify.applyAllLinks(quotedText, status.account_key, layoutPosition.toLong(),
status.is_possibly_sensitive, skipLinksInText)
if (quotedDisplayEnd != -1 && quotedDisplayEnd <= quotedText.length) {
itemView.quotedText.text = quotedText.subSequence(0, quotedDisplayEnd)
itemView.quotedText.spannable = quotedText.subSequence(0, quotedDisplayEnd)
} else {
itemView.quotedText.text = quotedText
}
if (itemView.quotedText.length() == 0) {
// No text
itemView.quotedText.visibility = View.GONE
} else {
itemView.quotedText.visibility = View.VISIBLE
itemView.quotedText.spannable = quotedText
}
itemView.quotedText.hideIfEmpty()
val quotedUserColor = colorNameManager.getUserColor(status.quoted_user_key!!)
if (quotedUserColor != 0) {
@ -821,7 +813,7 @@ class StatusFragment : BaseFragment(), LoaderCallbacks<SingleResponse<Parcelable
string.setSpan(ForegroundColorSpan(ThemeUtils.getColorFromAttribute(context,
android.R.attr.textColorTertiary, textView.currentTextColor)), 0,
string.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
itemView.quotedText.text = string
itemView.quotedText.spannable = string
itemView.quotedView.drawStart(ThemeUtils.getColorFromAttribute(context,
R.attr.quoteIndicatorBackgroundColor))
@ -864,7 +856,7 @@ class StatusFragment : BaseFragment(), LoaderCallbacks<SingleResponse<Parcelable
val timeString = Utils.formatToLongTimeString(context, timestamp)?.takeIf(String::isNotEmpty)
val source = status.source?.takeIf(String::isNotEmpty)
itemView.timeSource.text = when {
itemView.timeSource.spannable = when {
timeString != null && source != null -> {
HtmlSpanBuilder.fromHtml(context.getString(R.string.status_format_time_source,
timeString, source))
@ -882,13 +874,13 @@ class StatusFragment : BaseFragment(), LoaderCallbacks<SingleResponse<Parcelable
status.is_possibly_sensitive, skipLinksInText)
}
summaryView.text = status.extras?.summary_text
summaryView.spannable = status.extras?.summary_text
summaryView.hideIfEmpty()
if (displayEnd != -1 && displayEnd <= text.length) {
textView.text = text.subSequence(0, displayEnd)
textView.spannable = text.subSequence(0, displayEnd)
} else {
textView.text = text
textView.spannable = text
}
textView.hideIfEmpty()
@ -897,7 +889,7 @@ class StatusFragment : BaseFragment(), LoaderCallbacks<SingleResponse<Parcelable
if (!TextUtils.isEmpty(placeFullName)) {
locationView.visibility = View.VISIBLE
locationView.text = placeFullName
locationView.spannable = placeFullName
locationView.isClickable = ParcelableLocationUtils.isValidLocation(location)
} else if (ParcelableLocationUtils.isValidLocation(location)) {
locationView.visibility = View.VISIBLE
@ -905,7 +897,7 @@ class StatusFragment : BaseFragment(), LoaderCallbacks<SingleResponse<Parcelable
locationView.isClickable = true
} else {
locationView.visibility = View.GONE
locationView.text = null
locationView.spannable = null
}
val interactUsersAdapter = itemView.countsUsers.adapter as CountsUsersAdapter

View File

@ -69,6 +69,7 @@ import android.view.*
import android.view.View.OnClickListener
import android.view.View.OnTouchListener
import android.view.animation.AnimationUtils
import android.widget.TextView
import android.widget.Toast
import com.bumptech.glide.Glide
import com.squareup.otto.Subscribe
@ -429,11 +430,10 @@ class UserFragment : BaseFragment(), OnClickListener, OnLinkClickListener,
this.user = user
profileImage.setBorderColor(if (user.color != 0) user.color else Color.WHITE)
profileNameContainer.drawEnd(user.account_color)
profileNameContainer.name.text = bidiFormatter.unicodeWrap(if (user.nickname.isNullOrEmpty()) {
user.name
} else {
getString(R.string.name_with_nickname, user.name, user.nickname)
})
profileNameContainer.name.setText(bidiFormatter.unicodeWrap(when {
user.nickname.isNullOrEmpty() -> user.name
else -> getString(R.string.name_with_nickname, user.name, user.nickname)
}), TextView.BufferType.SPANNABLE)
val typeIconRes = Utils.getUserTypeIconRes(user.is_verified, user.is_protected)
if (typeIconRes != 0) {
profileType.setImageResource(typeIconRes)
@ -443,7 +443,7 @@ class UserFragment : BaseFragment(), OnClickListener, OnLinkClickListener,
profileType.visibility = View.GONE
}
@SuppressLint("SetTextI18n")
profileNameContainer.screenName.text = "@${user.acct}"
profileNameContainer.screenName.spannable = "@${user.acct}"
val linkHighlightOption = preferences[linkHighlightOptionKey]
val linkify = TwidereLinkify(this, linkHighlightOption)
if (user.description_unescaped != null) {
@ -451,22 +451,22 @@ class UserFragment : BaseFragment(), OnClickListener, OnLinkClickListener,
user.description_spans?.applyTo(this)
linkify.applyAllLinks(this, user.account_key, false, false)
}
descriptionContainer.description.text = text
descriptionContainer.description.spannable = text
} else {
descriptionContainer.description.text = user.description_plain
descriptionContainer.description.spannable = user.description_plain
Linkify.addLinks(descriptionContainer.description, Linkify.WEB_URLS)
}
descriptionContainer.visibility = if (descriptionContainer.description.empty) View.GONE else View.VISIBLE
descriptionContainer.hideIfEmpty(descriptionContainer.description)
locationContainer.location.text = user.location
locationContainer.location.spannable = user.location
locationContainer.visibility = if (locationContainer.location.empty) View.GONE else View.VISIBLE
urlContainer.url.text = user.urlPreferred?.let {
urlContainer.url.spannable = user.urlPreferred?.let {
val ssb = SpannableStringBuilder(it)
ssb.setSpan(TwidereURLSpan(it, highlightStyle = linkHighlightOption), 0, ssb.length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
return@let ssb
}
urlContainer.visibility = if (urlContainer.url.empty) View.GONE else View.VISIBLE
urlContainer.hideIfEmpty(urlContainer.url)
if (user.created_at >= 0) {
val createdAt = Utils.formatToLongTimeString(activity, user.created_at)
val daysSinceCreation = (System.currentTimeMillis() - user.created_at) / 1000 / 60 / 60 / 24.toFloat()
@ -509,8 +509,10 @@ class UserFragment : BaseFragment(), OnClickListener, OnLinkClickListener,
if (relationship == null) {
getFriendship()
}
activity.title = UserColorNameManager.decideDisplayName(user.nickname, user.name,
user.screen_name, nameFirst)
activity.title = SpannableStringBuilder.valueOf(UserColorNameManager.decideDisplayName(user.nickname, user.name,
user.screen_name, nameFirst)).also {
externalThemeManager.emoji?.applyTo(it)
}
val userCreationDay = condition@ if (user.created_at >= 0) {
val cal = Calendar.getInstance()

View File

@ -20,6 +20,7 @@ import android.widget.TextView
import kotlinx.android.synthetic.main.fragment_content_listview.*
import org.mariotaku.kpreferences.KPreferences
import org.mariotaku.ktextension.setItemAvailability
import org.mariotaku.ktextension.spannable
import org.mariotaku.library.objectcursor.ObjectCursor
import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants.*
@ -177,7 +178,7 @@ class FilteredUsersFragment : BaseFiltersFragment() {
val screenName = cursor.getString(indices[Filters.Users.SCREEN_NAME])
val displayName = userColorNameManager.getDisplayName(userKey, name, screenName,
nameFirst)
text1.text = displayName
text1.spannable = displayName
val ssb = SpannableStringBuilder(displayName)
if (cursor.getLong(indices[Filters.Users.SOURCE]) >= 0) {
@ -188,8 +189,8 @@ class FilteredUsersFragment : BaseFiltersFragment() {
drawable.setColorFilter(secondaryTextColor, PorterDuff.Mode.SRC_ATOP)
ssb.setSpan(EmojiSpan(drawable), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
text1.text = ssb
text2.text = userKey.host
text1.spannable = ssb
text2.spannable = userKey.host
}
override fun swapCursor(c: Cursor?): Cursor? {

View File

@ -57,6 +57,7 @@ import org.mariotaku.chameleon.ChameleonUtils
import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.mapToArray
import org.mariotaku.ktextension.setItemAvailability
import org.mariotaku.ktextension.spannable
import org.mariotaku.ktextension.useCursor
import org.mariotaku.library.objectcursor.ObjectCursor
import org.mariotaku.microblog.library.MicroBlog
@ -260,14 +261,14 @@ class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment,
val profileImageStyle = preferences[profileImageStyleKey]
requestManager.loadProfileImage(context, data, profileImageStyle).into(conversationAvatar)
requestManager.loadProfileImage(context, data, profileImageStyle, size = ProfileImageSize.REASONABLY_SMALL).into(appBarIcon)
appBarTitle.text = name
conversationTitle.text = name
appBarTitle.spannable = name
conversationTitle.spannable = name
if (summary != null) {
appBarSubtitle.visibility = View.VISIBLE
conversationSubtitle.visibility = View.VISIBLE
appBarSubtitle.text = summary
conversationSubtitle.text = summary
appBarSubtitle.spannable = summary
conversationSubtitle.spannable = summary
} else {
appBarSubtitle.visibility = View.GONE
conversationSubtitle.visibility = View.GONE

View File

@ -493,10 +493,10 @@ class MessagesConversationFragment : AbsContentListRecyclerViewFragment<Messages
sendMessage.isEnabled = !readOnly
editText.isEnabled = !readOnly
conversationTitle.text = title
conversationTitle.spannable = title
if (subtitle != null) {
conversationSubtitle.visibility = View.VISIBLE
conversationSubtitle.text = subtitle
conversationSubtitle.spannable = subtitle
} else {
conversationSubtitle.visibility = View.GONE
}

View File

@ -32,6 +32,7 @@ import android.view.MenuInflater
import android.view.MenuItem
import android.widget.TextView
import org.mariotaku.chameleon.Chameleon
import org.mariotaku.ktextension.spannable
import org.mariotaku.twidere.Constants.*
import org.mariotaku.twidere.R
import org.mariotaku.twidere.activity.ComposeActivity
@ -105,7 +106,7 @@ class SearchFragment : AbsToolbarTabPagesFragment(), RefreshScrollTopInterface,
val customView = actionBar.customView
val editQuery = customView.findViewById(R.id.editQuery) as TextView
editQuery.setTextColor(ThemeUtils.getColorDependent(theme.colorToolbar))
editQuery.text = query
editQuery.spannable = query
customView.setOnClickListener {
val searchIntent = Intent(context, QuickSearchBarActivity::class.java).apply {
putExtra(EXTRA_QUERY, query)

View File

@ -17,33 +17,22 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.preference;
package org.mariotaku.twidere.preference
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.util.AttributeSet;
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.ResolveInfo
import android.util.AttributeSet
import java.util.List;
abstract class ActivityPickerPreference(context: Context, attrs: AttributeSet? = null) :
ComponentPickerPreference(context, attrs) {
public abstract class ActivityPickerPreference extends ComponentPickerPreference {
public ActivityPickerPreference(final Context context) {
this(context, null);
override fun getComponentName(info: ResolveInfo): ComponentName {
return ComponentName(info.activityInfo.packageName, info.activityInfo.name)
}
public ActivityPickerPreference(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
@Override
protected final ComponentName getComponentName(ResolveInfo info) {
return new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
}
@Override
protected List<ResolveInfo> resolve(Intent queryIntent) {
return packageManager.queryIntentActivities(queryIntent, 0);
override fun resolve(queryIntent: Intent): List<ResolveInfo> {
return packageManager.queryIntentActivities(queryIntent, 0)
}
}

View File

@ -0,0 +1,74 @@
/*
* 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.preference
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.util.AttributeSet
abstract class ComponentPickerPreference(context: Context, attrs: AttributeSet? = null) : ThemedListPreference(context, attrs) {
protected val packageManager: PackageManager = context.packageManager
init {
setup()
}
override fun getSummary(): CharSequence {
if (isNoneValue(value)) return noneEntry
return entry
}
protected abstract val intentAction: String
protected abstract val noneEntry: String
protected abstract fun getComponentName(info: ResolveInfo): ComponentName
protected abstract fun resolve(queryIntent: Intent): List<ResolveInfo>
private fun setup() {
val queryIntent = Intent(intentAction)
val infoList = resolve(queryIntent)
val infoListSize = infoList.size
val entries = arrayOfNulls<CharSequence>(infoListSize + 1)
val values = arrayOfNulls<CharSequence>(infoListSize + 1)
entries[0] = noneEntry
values[0] = ""
for (i in 0..infoListSize - 1) {
val info = infoList[i]
entries[i + 1] = info.loadLabel(packageManager)
values[i + 1] = getComponentName(info).flattenToString()
}
setEntries(entries)
entryValues = values
}
companion object {
fun isNoneValue(value: String?): Boolean {
return value.isNullOrEmpty() || "none" == value
}
}
}

View File

@ -0,0 +1,39 @@
/*
* 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.preference
import android.content.Context
import android.util.AttributeSet
import org.mariotaku.twidere.R
import org.mariotaku.twidere.constant.IntentConstants
/**
* Created by mariotaku on 15/12/22.
*/
class EmojiSupportPreference(context: Context, attrs: AttributeSet? = null) :
ActivityPickerPreference(context, attrs) {
override val intentAction: String
get() = IntentConstants.INTENT_ACTION_EMOJI_SUPPORT_ABOUT
override val noneEntry: String
get() = context.getString(R.string.system_default)
}

View File

@ -17,32 +17,22 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.preference;
package org.mariotaku.twidere.preference
import android.content.Context;
import android.util.AttributeSet;
import android.content.Context
import android.util.AttributeSet
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.R
public class StatusShortenerPreference extends ServicePickerPreference implements Constants {
import org.mariotaku.twidere.constant.IntentConstants.INTENT_ACTION_EXTENSION_UPLOAD_MEDIA
public StatusShortenerPreference(final Context context) {
this(context, null);
}
class MediaUploaderPreference(context: Context, attrs: AttributeSet? = null) :
ServicePickerPreference(context, attrs) {
public StatusShortenerPreference(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
override val intentAction: String
get() = INTENT_ACTION_EXTENSION_UPLOAD_MEDIA
@Override
protected String getIntentAction() {
return INTENT_ACTION_EXTENSION_SHORTEN_STATUS;
}
@Override
protected String getNoneEntry() {
return getContext().getString(R.string.status_shortener_default);
}
override val noneEntry: String
get() = context.getString(R.string.media_uploader_default)
}

View File

@ -17,33 +17,22 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.preference;
package org.mariotaku.twidere.preference
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.util.AttributeSet;
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.ResolveInfo
import android.util.AttributeSet
import java.util.List;
abstract class ServicePickerPreference(context: Context, attrs: AttributeSet?) :
ComponentPickerPreference(context, attrs) {
public abstract class ServicePickerPreference extends ComponentPickerPreference {
public ServicePickerPreference(final Context context) {
this(context, null);
override fun getComponentName(info: ResolveInfo): ComponentName {
return ComponentName(info.serviceInfo.packageName, info.serviceInfo.name)
}
public ServicePickerPreference(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
@Override
protected final ComponentName getComponentName(ResolveInfo info) {
return new ComponentName(info.serviceInfo.packageName, info.serviceInfo.name);
}
@Override
protected List<ResolveInfo> resolve(Intent queryIntent) {
return packageManager.queryIntentServices(queryIntent, 0);
override fun resolve(queryIntent: Intent): List<ResolveInfo> {
return packageManager.queryIntentServices(queryIntent, 0)
}
}

View File

@ -17,33 +17,21 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.preference;
package org.mariotaku.twidere.preference
import android.content.Context;
import android.util.AttributeSet;
import android.content.Context
import android.util.AttributeSet
import org.mariotaku.twidere.Constants
import org.mariotaku.twidere.R
import org.mariotaku.twidere.constant.IntentConstants.INTENT_ACTION_EXTENSION_SHORTEN_STATUS
import org.mariotaku.twidere.R;
class StatusShortenerPreference(context: Context, attrs: AttributeSet?) :
ServicePickerPreference(context, attrs), Constants {
import static org.mariotaku.twidere.constant.IntentConstants.INTENT_ACTION_EXTENSION_UPLOAD_MEDIA;
override val intentAction: String
get() = INTENT_ACTION_EXTENSION_SHORTEN_STATUS
public class MediaUploaderPreference extends ServicePickerPreference {
public MediaUploaderPreference(final Context context) {
super(context);
}
public MediaUploaderPreference(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
@Override
protected String getIntentAction() {
return INTENT_ACTION_EXTENSION_UPLOAD_MEDIA;
}
@Override
protected String getNoneEntry() {
return getContext().getString(R.string.media_uploader_default);
}
override val noneEntry: String
get() = context.getString(R.string.status_shortener_default)
}

View File

@ -0,0 +1,22 @@
package org.mariotaku.twidere.preference
import android.content.Context
import android.support.v7.preference.ListPreference
import android.support.v7.preference.PreferenceFragmentCompat
import android.util.AttributeSet
import org.mariotaku.twidere.fragment.ThemedListPreferenceDialogFragmentCompat
import org.mariotaku.twidere.preference.iface.IDialogPreference
/**
* Created by mariotaku on 16/3/15.
*/
open class ThemedListPreference(context: Context, attrs: AttributeSet? = null) :
ListPreference(context, attrs), IDialogPreference {
override fun displayDialog(fragment: PreferenceFragmentCompat) {
val df = ThemedListPreferenceDialogFragmentCompat.newInstance(key)
df.setTargetFragment(fragment, 0)
df.show(fragment.fragmentManager, key)
}
}

View File

@ -51,7 +51,7 @@ import org.mariotaku.twidere.model.account.AccountExtras
import org.mariotaku.twidere.model.analyzer.UpdateStatus
import org.mariotaku.twidere.model.schedule.ScheduleInfo
import org.mariotaku.twidere.model.util.ParcelableLocationUtils
import org.mariotaku.twidere.preference.ServicePickerPreference
import org.mariotaku.twidere.preference.ComponentPickerPreference
import org.mariotaku.twidere.provider.TwidereDataStore.Drafts
import org.mariotaku.twidere.task.BaseAbstractTask
import org.mariotaku.twidere.util.*
@ -496,7 +496,7 @@ class UpdateStatusTask(
@Throws(UploaderNotFoundException::class, UploadException::class, ShortenerNotFoundException::class, ShortenException::class)
private fun getStatusShortener(app: TwidereApplication): StatusShortenerInterface? {
val shortenerComponent = preferences.getString(KEY_STATUS_SHORTENER, null)
if (ServicePickerPreference.isNoneValue(shortenerComponent)) return null
if (ComponentPickerPreference.isNoneValue(shortenerComponent)) return null
val shortener = StatusShortenerInterface.getInstance(app, shortenerComponent) ?: throw ShortenerNotFoundException()
try {
@ -518,7 +518,7 @@ class UpdateStatusTask(
@Throws(UploaderNotFoundException::class, UploadException::class)
private fun getMediaUploader(app: TwidereApplication): MediaUploaderInterface? {
val uploaderComponent = preferences.getString(KEY_MEDIA_UPLOADER, null)
if (ServicePickerPreference.isNoneValue(uploaderComponent)) return null
if (ComponentPickerPreference.isNoneValue(uploaderComponent)) return null
val uploader = MediaUploaderInterface.getInstance(app, uploaderComponent) ?:
throw UploaderNotFoundException(context.getString(R.string.error_message_media_uploader_not_found))
try {

View File

@ -23,12 +23,10 @@ import android.text.Editable
import android.text.Spanned
import android.text.TextWatcher
import android.widget.TextView
import org.mariotaku.twidere.extension.applyTo
import org.mariotaku.twidere.text.SafeSpannableStringBuilder
import org.mariotaku.twidere.util.EmojiSupportUtils
import org.mariotaku.twidere.util.ExternalThemeManager
import org.mariotaku.twidere.util.dagger.GeneralComponent
import javax.inject.Inject
/**
@ -45,7 +43,8 @@ class EmojiEditableFactory(textView: TextView) : Editable.Factory() {
override fun newEditable(source: CharSequence): Editable {
val editable = SafeSpannableStringBuilder(source)
EmojiSupportUtils.applyEmoji(externalThemeManager, editable)
val emoji = externalThemeManager.emoji
emoji?.applyTo(editable)
editable.setSpan(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
@ -53,7 +52,7 @@ class EmojiEditableFactory(textView: TextView) : Editable.Factory() {
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
if (count <= 0) return
EmojiSupportUtils.applyEmoji(externalThemeManager, editable, start, count)
emoji?.applyTo(editable)
}
override fun afterTextChanged(s: Editable) {

View File

@ -21,12 +21,10 @@ package org.mariotaku.twidere.text.util
import android.text.Spannable
import android.widget.TextView
import org.mariotaku.twidere.extension.applyTo
import org.mariotaku.twidere.text.SafeSpannableString
import org.mariotaku.twidere.util.EmojiSupportUtils
import org.mariotaku.twidere.util.ExternalThemeManager
import org.mariotaku.twidere.util.dagger.GeneralComponent
import javax.inject.Inject
/**
@ -43,7 +41,7 @@ class EmojiSpannableFactory(textView: TextView) : Spannable.Factory() {
override fun newSpannable(source: CharSequence): Spannable {
val spannable = SafeSpannableString(source)
EmojiSupportUtils.applyEmoji(externalThemeManager, spannable)
externalThemeManager.emoji?.applyTo(spannable)
return spannable
}
}

View File

@ -1,159 +0,0 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2015 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
import android.graphics.drawable.Drawable
import android.text.Spannable
import android.text.Spanned
import android.widget.TextView
import org.mariotaku.commons.text.CodePointArray
import org.mariotaku.commons.text.get
import org.mariotaku.twidere.text.style.EmojiSpan
import org.mariotaku.twidere.text.util.EmojiEditableFactory
import org.mariotaku.twidere.text.util.EmojiSpannableFactory
/**
* Created by mariotaku on 15/12/20.
*/
object EmojiSupportUtils {
fun initForTextView(textView: TextView) {
if (textView.isInEditMode) return
textView.setSpannableFactory(EmojiSpannableFactory(textView))
textView.setEditableFactory(EmojiEditableFactory(textView))
}
fun applyEmoji(manager: ExternalThemeManager, text: Spannable,
textStart: Int = 0, textLength: Int = text.length) {
val emoji = manager.emoji
if (emoji == null || !emoji.isSupported) return
val array = CodePointArray(text)
var arrayIdx = array.length() - 1
while (arrayIdx >= 0) {
val codePoint = array[arrayIdx]
if (isEmoji(codePoint)) {
val arrayEnd = arrayIdx + 1
var arrayIdxOffset = 0
val textIdx = array.indexOfText(codePoint, arrayIdx)
var textIdxOffset = 0
var skippedIndex = 0
if (textIdx == -1 || textIdx < textStart) {
arrayIdx--
continue
}
val textEnd = textIdx + Character.charCount(codePoint)
if (arrayIdx > 0) {
val prevCodePoint = array[arrayIdx - 1]
when {
isRegionalIndicatorSymbol(codePoint) -> if (isRegionalIndicatorSymbol(prevCodePoint)) {
arrayIdxOffset = -1
textIdxOffset = -Character.charCount(prevCodePoint)
skippedIndex = -1
}
isModifier(codePoint) -> if (isEmoji(prevCodePoint)) {
arrayIdxOffset = -1
textIdxOffset = -Character.charCount(prevCodePoint)
skippedIndex = -1
}
isKeyCap(codePoint) -> if (isPhoneNumberSymbol(prevCodePoint)) {
arrayIdxOffset = -1
textIdxOffset = -Character.charCount(prevCodePoint)
skippedIndex = -1
}
isZeroWidthJoin(prevCodePoint) -> {
var notValidControlCount = 0
var charCount = 0
for (i in arrayIdx - 1 downTo 0) {
val cp = array.get(i)
charCount += Character.charCount(cp)
if (isZeroWidthJoin(cp) || isVariationSelector(cp)) {
// Ignore
notValidControlCount = 0
continue
}
notValidControlCount++
if (notValidControlCount > 1 || i == 0) {
arrayIdxOffset = i - arrayIdx + 1
textIdxOffset = -charCount + Character.charCount(cp)
skippedIndex = i - arrayIdx + 1
break
}
}
}
}
}
if (textEnd > textStart + textLength) {
arrayIdx--
continue
}
var spans = text.getSpans(textIdx + textIdxOffset, textEnd, EmojiSpan::class.java)
if (spans.isEmpty()) {
var drawable: Drawable? = emoji.getEmojiDrawableFor(*array[arrayIdx + arrayIdxOffset..arrayEnd])
if (drawable == null) {
// Not emoji combination, just use fallback
textIdxOffset = 0
arrayIdxOffset = 0
skippedIndex = 0
spans = text.getSpans(textIdx + textIdxOffset, textEnd, EmojiSpan::class.java)
if (spans.isEmpty()) {
drawable = emoji.getEmojiDrawableFor(*array[arrayIdx + arrayIdxOffset..arrayEnd])
}
}
if (drawable != null) {
text.setSpan(EmojiSpan(drawable), textIdx + textIdxOffset, textEnd,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
arrayIdx += skippedIndex
}
arrayIdx--
}
}
private fun isVariationSelector(codePoint: Int): Boolean {
return codePoint == 0xfe0f
}
private fun isZeroWidthJoin(codePoint: Int): Boolean {
return codePoint == 0x200d
}
private fun isPhoneNumberSymbol(codePoint: Int): Boolean {
return codePoint == 0x0023 || codePoint == 0x002a || codePoint in 0x0030..0x0039
}
private fun isModifier(codePoint: Int): Boolean {
return codePoint in 0x1f3fb..0x1f3ff
}
private fun isEmoji(codePoint: Int): Boolean {
return !Character.isLetterOrDigit(codePoint)
}
private fun isRegionalIndicatorSymbol(codePoint: Int): Boolean {
return codePoint in 0x1f1e6..0x1f1ff
}
private fun isKeyCap(codePoint: Int): Boolean {
return codePoint == 0x20e3
}
}

View File

@ -28,7 +28,6 @@ import android.graphics.drawable.Drawable
import android.support.v4.content.res.ResourcesCompat
import android.util.LruCache
import org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_EMOJI_SUPPORT
import java.util.*
/**
* Created by mariotaku on 15/12/20.
@ -105,7 +104,11 @@ class ExternalThemeManager(private val context: Context, private val preferences
if (i != 0) {
sb.append("_")
}
sb.append(String.format(Locale.US, "%04x", codePoints[i]))
val hex = Integer.toHexString(codePoints[i])
for (j in 0 until 4 - hex.length) {
sb.append("0")
}
sb.append(hex)
}
val identifier = resources.getIdentifier(sb.toString(),
if (useMipmap) "mipmap" else "drawable", packageName)

View File

@ -151,8 +151,8 @@ interface GeneralComponent {
fun inject(service: BaseService)
companion object {
private var instance: GeneralComponent? = null
private var instance: GeneralComponent? = null
fun get(context: Context): GeneralComponent {
return instance ?: run {
val helper = DaggerGeneralComponent.builder().applicationModule(ApplicationModule.get(context)).build()
@ -160,5 +160,6 @@ interface GeneralComponent {
return@run helper
}
}
}
}

View File

@ -36,8 +36,8 @@ import com.bumptech.glide.Glide
import org.mariotaku.chameleon.view.ChameleonMultiAutoCompleteTextView
import org.mariotaku.ktextension.contains
import org.mariotaku.twidere.adapter.ComposeAutoCompleteAdapter
import org.mariotaku.twidere.extension.setupEmojiFactory
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.util.EmojiSupportUtils
import org.mariotaku.twidere.util.widget.StatusTextTokenizer
@ -55,7 +55,7 @@ class ComposeEditText(
}
init {
EmojiSupportUtils.initForTextView(this)
setupEmojiFactory()
setTokenizer(StatusTextTokenizer())
onItemClickListener = AdapterView.OnItemClickListener { parent, view, position, id ->
removeIMESuggestions()

View File

@ -3,8 +3,7 @@ package org.mariotaku.twidere.view
import android.content.Context
import android.util.AttributeSet
import org.mariotaku.chameleon.view.ChameleonEditText
import org.mariotaku.twidere.text.util.SafeEditableFactory
import org.mariotaku.twidere.text.util.SafeSpannableFactory
import org.mariotaku.twidere.extension.setupEmojiFactory
/**
* Created by mariotaku on 2017/2/3.
@ -13,8 +12,7 @@ import org.mariotaku.twidere.text.util.SafeSpannableFactory
class FixedEditText(context: Context, attrs: AttributeSet? = null) : ChameleonEditText(context, attrs) {
init {
setSpannableFactory(SafeSpannableFactory)
setEditableFactory(SafeEditableFactory)
setupEmojiFactory()
}
override fun onTextContextMenuItem(id: Int): Boolean {

View File

@ -3,8 +3,7 @@ package org.mariotaku.twidere.view
import android.content.Context
import android.util.AttributeSet
import org.mariotaku.chameleon.view.ChameleonTextView
import org.mariotaku.twidere.text.util.SafeEditableFactory
import org.mariotaku.twidere.text.util.SafeSpannableFactory
import org.mariotaku.twidere.extension.setupEmojiFactory
/**
* Created by mariotaku on 2017/2/3.
@ -13,8 +12,7 @@ import org.mariotaku.twidere.text.util.SafeSpannableFactory
open class FixedTextView(context: Context, attrs: AttributeSet? = null) : ChameleonTextView(context, attrs) {
init {
setSpannableFactory(SafeSpannableFactory)
setEditableFactory(SafeEditableFactory)
setupEmojiFactory()
}
override fun onTextContextMenuItem(id: Int): Boolean {
@ -25,4 +23,5 @@ open class FixedTextView(context: Context, attrs: AttributeSet? = null) : Chamel
return true
}
}
}

View File

@ -31,7 +31,6 @@ import android.text.style.StyleSpan
import android.util.AttributeSet
import android.util.TypedValue
import org.mariotaku.twidere.R
import org.mariotaku.twidere.util.EmojiSupportUtils
/**
* Created by mariotaku on 15/5/28.
@ -60,7 +59,6 @@ class NameView(context: Context, attrs: AttributeSet? = null) : FixedTextView(co
private var secondaryTextSize: AbsoluteSizeSpan? = null
init {
EmojiSupportUtils.initForTextView(this)
ellipsize = TextUtils.TruncateAt.END
val a = context.obtainStyledAttributes(attrs, R.styleable.NameView, 0, 0)
setPrimaryTextColor(a.getColor(R.styleable.NameView_nv_primaryTextColor, 0))
@ -123,7 +121,7 @@ class NameView(context: Context, attrs: AttributeSet? = null) : FixedTextView(co
sb.setSpan(secondaryTextStyle, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
sb.setSpan(secondaryTextSize, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
text = sb
setText(sb, BufferType.SPANNABLE)
}
fun setPrimaryTextSize(textSize: Float) {

View File

@ -28,7 +28,7 @@ import android.view.KeyEvent
import android.view.MotionEvent
import android.widget.TextView
import org.mariotaku.chameleon.view.ChameleonTextView
import org.mariotaku.twidere.util.EmojiSupportUtils
import org.mariotaku.twidere.extension.setupEmojiFactory
/**
* Returns true when not clicking links
@ -41,7 +41,7 @@ class TimelineContentTextView @JvmOverloads constructor(
) : ChameleonTextView(context, attrs, defStyle) {
init {
EmojiSupportUtils.initForTextView(this)
setupEmojiFactory()
}
override fun dispatchTouchEvent(event: MotionEvent): Boolean {

View File

@ -34,6 +34,7 @@ import nl.komponents.kovenant.task
import nl.komponents.kovenant.ui.successUi
import org.mariotaku.abstask.library.AbstractTask
import org.mariotaku.abstask.library.TaskStarter
import org.mariotaku.ktextension.spannable
import org.mariotaku.ktextension.toLongOr
import org.mariotaku.microblog.library.MicroBlogException
import org.mariotaku.microblog.library.twitter.TwitterCaps
@ -189,7 +190,7 @@ class CardPollViewController : ContainerView.ViewController() {
val value = card.getAsInteger("choice${choiceIndex}_count", 0)
if (label == null) throw NullPointerException()
val choicePercent = if (votesSum == 0) 0f else value / votesSum.toFloat()
choiceLabelView.text = label
choiceLabelView.spannable = label
choicePercentView.text = String.format(Locale.US, "%d%%", Math.round(choicePercent * 100))
pollItem.setOnClickListener(clickListener)
@ -214,7 +215,7 @@ class CardPollViewController : ContainerView.ViewController() {
val nVotes = context.resources.getQuantityString(R.plurals.N_votes, votesSum, votesSum)
val timeLeft = DateUtils.getRelativeTimeSpanString(context, endDatetimeUtc.time, true)
view.pollSummary.text = context.getString(R.string.poll_summary_format, nVotes, timeLeft)
view.pollSummary.spannable = context.getString(R.string.poll_summary_format, nVotes, timeLeft)
}
private class PercentDrawable internal constructor(

View File

@ -19,13 +19,12 @@
package org.mariotaku.twidere.view.holder
import android.annotation.SuppressLint
import android.support.v7.widget.RecyclerView
import android.view.View
import android.widget.CompoundButton
import android.widget.ImageView
import android.widget.TextView
import org.mariotaku.ktextension.spannable
import org.mariotaku.twidere.R
import org.mariotaku.twidere.adapter.AccountDetailsAdapter
import org.mariotaku.twidere.extension.loadProfileImage
@ -60,10 +59,9 @@ class AccountViewHolder(
dragHandle.visibility = if (enabled) View.VISIBLE else View.GONE
}
@SuppressLint("SetTextI18n")
fun display(details: AccountDetails) {
name.text = details.user.name
screenName.text = "@${details.user.screen_name}"
name.spannable = details.user.name
screenName.spannable = "@${details.user.screen_name}"
setAccountColor(details.color)
profileImage.visibility = View.VISIBLE
adapter.requestManager.loadProfileImage(adapter.context, details, adapter.profileImageStyle,

View File

@ -25,6 +25,7 @@ import android.view.View
import com.bumptech.glide.RequestManager
import kotlinx.android.synthetic.main.list_item_draft.view.*
import org.mariotaku.ktextension.mapToArray
import org.mariotaku.ktextension.spannable
import org.mariotaku.twidere.R
import org.mariotaku.twidere.extension.model.getActionName
import org.mariotaku.twidere.model.Draft
@ -76,11 +77,11 @@ class DraftViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
contentView.drawEnd()
}
if (summaryText != null) {
textView.text = summaryText
textView.spannable = summaryText
} else if (draft.text.isNullOrEmpty()) {
textView.setText(R.string.empty_content)
} else {
textView.text = draft.text
textView.spannable = draft.text
}
if (draft.timestamp > 0) {

View File

@ -20,9 +20,10 @@
package org.mariotaku.twidere.view.holder
import android.support.v7.widget.RecyclerView.ViewHolder
import android.text.TextUtils
import android.view.View
import kotlinx.android.synthetic.main.card_item_group_compact.view.*
import org.mariotaku.ktextension.hideIfEmpty
import org.mariotaku.ktextension.spannable
import org.mariotaku.ktextension.toLocalizedString
import org.mariotaku.twidere.R
import org.mariotaku.twidere.adapter.iface.IGroupsAdapter
@ -63,7 +64,7 @@ class GroupViewHolder(private val adapter: IGroupsAdapter<*>, itemView: View) :
externalIndicator.visibility = View.GONE
} else {
externalIndicator.visibility = View.VISIBLE
externalIndicator.text = context.getString(R.string.external_group_host_format,
externalIndicator.spannable = context.getString(R.string.external_group_host_format,
groupHost)
}
if (adapter.profileImageEnabled) {
@ -74,8 +75,8 @@ class GroupViewHolder(private val adapter: IGroupsAdapter<*>, itemView: View) :
} else {
profileImageView.visibility = View.GONE
}
descriptionView.visibility = if (TextUtils.isEmpty(group.description)) View.GONE else View.VISIBLE
descriptionView.text = formatter.unicodeWrap(group.description)
descriptionView.spannable = formatter.unicodeWrap(group.description)
descriptionView.hideIfEmpty()
membersCountView.text = group.member_count.toLocalizedString()
adminsCountView.text = group.admin_count.toLocalizedString()
}

View File

@ -24,6 +24,7 @@ import android.view.View
import android.widget.ImageView
import com.commonsware.cwac.layouts.AspectLockedFrameLayout
import kotlinx.android.synthetic.main.adapter_item_media_status.view.*
import org.mariotaku.ktextension.spannable
import org.mariotaku.twidere.R
import org.mariotaku.twidere.adapter.iface.IStatusesAdapter
import org.mariotaku.twidere.extension.loadProfileImage
@ -62,9 +63,9 @@ class MediaStatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView:
val displayEnd = status.extras?.display_text_range?.getOrNull(1) ?: -1
if (displayEnd >= 0) {
mediaTextView.text = status.text_unescaped.subSequence(0, displayEnd)
mediaTextView.spannable = status.text_unescaped.subSequence(0, displayEnd)
} else {
mediaTextView.text = status.text_unescaped
mediaTextView.spannable = status.text_unescaped
}
adapter.requestManager.loadProfileImage(context, status,
adapter.profileImageStyle, profileImageView.cornerRadius,

View File

@ -4,6 +4,7 @@ import android.support.v7.widget.RecyclerView
import android.view.View
import android.widget.TextView
import kotlinx.android.synthetic.main.list_item_simple_user_list.view.*
import org.mariotaku.ktextension.spannable
import org.mariotaku.twidere.R
import org.mariotaku.twidere.adapter.iface.IUserListsAdapter
import org.mariotaku.twidere.extension.loadProfileImage
@ -28,8 +29,8 @@ class SimpleUserListViewHolder(
}
fun display(userList: ParcelableUserList) {
nameView.text = userList.name
createdByView.text = createdByView.context.getString(R.string.created_by,
nameView.spannable = userList.name
createdByView.spannable = createdByView.context.getString(R.string.created_by,
adapter.userColorNameManager.getDisplayName(userList, false))
if (adapter.profileImageEnabled) {
profileImageView.visibility = View.VISIBLE

View File

@ -5,6 +5,7 @@ import android.view.View
import android.widget.CheckBox
import android.widget.TextView
import kotlinx.android.synthetic.main.list_item_simple_user.view.*
import org.mariotaku.ktextension.spannable
import org.mariotaku.twidere.R
import org.mariotaku.twidere.adapter.iface.IContentAdapter
import org.mariotaku.twidere.extension.loadProfileImage
@ -30,8 +31,8 @@ open class SimpleUserViewHolder<out A : IContentAdapter>(
}
open fun displayUser(user: ParcelableUser) {
nameView.text = user.name
secondaryNameView.text = "@${user.screen_name}"
nameView.spannable = user.name
secondaryNameView.spannable = "@${user.screen_name}"
if (adapter.profileImageEnabled) {
val context = itemView.context
adapter.requestManager.loadProfileImage(context, user, adapter.profileImageStyle,

View File

@ -17,10 +17,7 @@ import android.widget.ImageView
import android.widget.TextView
import com.bumptech.glide.RequestManager
import kotlinx.android.synthetic.main.list_item_status.view.*
import org.mariotaku.ktextension.applyFontFamily
import org.mariotaku.ktextension.empty
import org.mariotaku.ktextension.hideIfEmpty
import org.mariotaku.ktextension.isNotNullOrEmpty
import org.mariotaku.ktextension.*
import org.mariotaku.microblog.library.mastodon.annotation.StatusVisibility
import org.mariotaku.twidere.Constants.*
import org.mariotaku.twidere.R
@ -116,12 +113,12 @@ class StatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View)
nameView.updateText(adapter.bidiFormatter)
summaryView.hideIfEmpty()
if (adapter.linkHighlightingStyle == VALUE_LINK_HIGHLIGHT_OPTION_CODE_NONE) {
textView.text = toPlainText(TWIDERE_PREVIEW_TEXT_HTML)
textView.spannable = toPlainText(TWIDERE_PREVIEW_TEXT_HTML)
} else {
val linkify = adapter.twidereLinkify
val text = HtmlSpanBuilder.fromHtml(TWIDERE_PREVIEW_TEXT_HTML)
linkify.applyAllLinks(text, null, -1, false, adapter.linkHighlightingStyle, true)
textView.text = text
textView.spannable = text
}
timeView.time = System.currentTimeMillis()
val showCardActions = isCardActionsShown
@ -177,7 +174,7 @@ class StatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View)
} else if (status.retweet_id != null) {
val retweetedBy = colorNameManager.getDisplayName(status.retweeted_by_user_key!!,
status.retweeted_by_user_name, status.retweeted_by_user_acct!!, nameFirst)
statusInfoLabel.text = context.getString(R.string.name_retweeted, formatter.unicodeWrap(retweetedBy))
statusInfoLabel.spannable = context.getString(R.string.name_retweeted, formatter.unicodeWrap(retweetedBy))
statusInfoIcon.setImageResource(R.drawable.ic_activity_action_retweet)
statusInfoLabel.visibility = View.VISIBLE
statusInfoIcon.visibility = View.VISIBLE
@ -187,9 +184,9 @@ class StatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View)
if (status.in_reply_to_name != null && status.in_reply_to_screen_name != null) {
val inReplyTo = colorNameManager.getDisplayName(status.in_reply_to_user_key!!,
status.in_reply_to_name, status.in_reply_to_screen_name, nameFirst)
statusInfoLabel.text = context.getString(R.string.in_reply_to_name, formatter.unicodeWrap(inReplyTo))
statusInfoLabel.spannable = context.getString(R.string.in_reply_to_name, formatter.unicodeWrap(inReplyTo))
} else {
statusInfoLabel.text = context.getString(R.string.label_status_type_reply)
statusInfoLabel.spannable = context.getString(R.string.label_status_type_reply)
}
statusInfoIcon.setImageResource(R.drawable.ic_activity_action_reply)
statusInfoLabel.visibility = View.VISIBLE
@ -231,9 +228,9 @@ class StatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View)
quotedText = status.quoted_text_unescaped
}
if (quotedDisplayEnd != -1 && quotedDisplayEnd <= quotedText.length) {
quotedTextView.text = quotedText.subSequence(0, quotedDisplayEnd)
quotedTextView.spannable = quotedText.subSequence(0, quotedDisplayEnd)
} else {
quotedTextView.text = quotedText
quotedTextView.spannable = quotedText
}
if (quotedTextView.length() == 0) {
@ -263,7 +260,7 @@ class StatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View)
quotedMediaLabel.visibility = View.GONE
}
quotedTextView.text = if (!quoteContentAvailable) {
quotedTextView.spannable = if (!quoteContentAvailable) {
// Display 'not available' label
SpannableString.valueOf(context.getString(R.string.label_status_not_available)).apply {
setSpan(ForegroundColorSpan(ThemeUtils.getColorFromAttribute(context,
@ -359,7 +356,7 @@ class StatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View)
mediaPreview.visibility = View.GONE
}
summaryView.text = status.extras?.summary_text
summaryView.spannable = status.extras?.summary_text
summaryView.hideIfEmpty()
val text: CharSequence
@ -385,19 +382,18 @@ class StatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View)
}
if (displayEnd != -1 && displayEnd <= text.length) {
textView.text = text.subSequence(0, displayEnd)
textView.spannable = text.subSequence(0, displayEnd)
} else {
textView.text = text
textView.spannable = text
}
textView.hideIfEmpty()
if (replyCount > 0) {
replyCountView.text = UnitConvertUtils.calculateProperCount(replyCount)
replyCountView.visibility = View.VISIBLE
replyCountView.spannable = UnitConvertUtils.calculateProperCount(replyCount)
} else {
replyCountView.text = null
replyCountView.visibility = View.GONE
replyCountView.spannable = null
}
replyCountView.hideIfEmpty()
when (status.extras?.visibility) {
StatusVisibility.PRIVATE -> {
@ -428,12 +424,12 @@ class StatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View)
retweetCount = status.retweet_count
if (retweetCount > 0) {
retweetCountView.text = UnitConvertUtils.calculateProperCount(retweetCount)
retweetCountView.visibility = View.VISIBLE
retweetCountView.spannable = UnitConvertUtils.calculateProperCount(retweetCount)
} else {
retweetCountView.text = null
retweetCountView.visibility = View.GONE
retweetCountView.spannable = null
}
retweetCountView.hideIfEmpty()
if (DestroyFavoriteTask.isDestroyingFavorite(status.account_key, status.id)) {
favoriteIcon.isActivated = false
} else {
@ -441,13 +437,13 @@ class StatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View)
favoriteIcon.isActivated = creatingFavorite || status.is_favorite
}
favoriteCount = status.favorite_count
if (favoriteCount > 0) {
favoriteCountView.text = UnitConvertUtils.calculateProperCount(favoriteCount)
favoriteCountView.visibility = View.VISIBLE
favoriteCountView.spannable = UnitConvertUtils.calculateProperCount(favoriteCount)
} else {
favoriteCountView.text = null
favoriteCountView.visibility = View.GONE
favoriteCountView.spannable = null
}
favoriteCountView.hideIfEmpty()
nameView.updateText(formatter)
quotedNameView.updateText(formatter)

View File

@ -20,10 +20,11 @@
package org.mariotaku.twidere.view.holder
import android.support.v7.widget.RecyclerView.ViewHolder
import android.text.TextUtils
import android.view.View
import android.widget.TextView
import kotlinx.android.synthetic.main.list_item_user_list.view.*
import org.mariotaku.ktextension.hideIfEmpty
import org.mariotaku.ktextension.spannable
import org.mariotaku.twidere.R
import org.mariotaku.twidere.adapter.iface.IUserListsAdapter
import org.mariotaku.twidere.extension.loadProfileImage
@ -68,10 +69,10 @@ class UserListViewHolder(
val manager = adapter.userColorNameManager
itemContent.drawStart(manager.getUserColor(userList.user_key))
nameView.text = userList.name
nameView.spannable = userList.name
val nameFirst = adapter.nameFirst
val createdByDisplayName = manager.getDisplayName(userList, nameFirst)
createdByView.text = context.getString(R.string.created_by, createdByDisplayName)
createdByView.spannable = context.getString(R.string.created_by, createdByDisplayName)
if (adapter.profileImageEnabled) {
profileImageView.visibility = View.VISIBLE
@ -80,8 +81,8 @@ class UserListViewHolder(
} else {
profileImageView.visibility = View.GONE
}
descriptionView.visibility = if (TextUtils.isEmpty(userList.description)) View.GONE else View.VISIBLE
descriptionView.text = userList.description
descriptionView.spannable = userList.description
descriptionView.hideIfEmpty()
membersCountView.text = Utils.getLocalizedNumber(Locale.getDefault(), userList.members_count)
subscribersCountView.text = Utils.getLocalizedNumber(Locale.getDefault(), userList.subscribers_count)
}

View File

@ -26,6 +26,7 @@ import android.view.View.OnClickListener
import android.view.View.OnLongClickListener
import android.widget.RelativeLayout
import kotlinx.android.synthetic.main.list_item_user.view.*
import org.mariotaku.ktextension.spannable
import org.mariotaku.twidere.R
import org.mariotaku.twidere.adapter.iface.IUsersAdapter
import org.mariotaku.twidere.adapter.iface.IUsersAdapter.*
@ -166,11 +167,11 @@ class UserViewHolder(
if (!simple) {
descriptionView.visibility = if (TextUtils.isEmpty(user.description_unescaped)) View.GONE else View.VISIBLE
descriptionView.text = user.description_unescaped
descriptionView.spannable = user.description_unescaped
locationView.visibility = if (TextUtils.isEmpty(user.location)) View.GONE else View.VISIBLE
locationView.text = user.location
locationView.spannable = user.location
urlView.visibility = if (TextUtils.isEmpty(user.url_expanded)) View.GONE else View.VISIBLE
urlView.text = user.url_expanded
urlView.spannable = user.url_expanded
val locale = Locale.getDefault()
statusesCountView.text = Utils.getLocalizedNumber(locale, user.statuses_count)
followersCountView.text = Utils.getLocalizedNumber(locale, user.followers_count)

View File

@ -22,6 +22,7 @@ package org.mariotaku.twidere.view.holder.message
import android.support.v7.widget.RecyclerView
import android.view.View
import kotlinx.android.synthetic.main.list_item_message_entry.view.*
import org.mariotaku.ktextension.spannable
import org.mariotaku.twidere.R
import org.mariotaku.twidere.adapter.MessagesEntriesAdapter
import org.mariotaku.twidere.extension.loadProfileImage
@ -80,8 +81,8 @@ class MessageEntryViewHolder(itemView: View, val adapter: MessagesEntriesAdapter
this.name.name = name
this.name.screenName = secondaryName
this.name.updateText(adapter.bidiFormatter)
this.text.text = conversation.getSummaryText(itemView.context, adapter.userColorNameManager,
adapter.nameFirst)
this.text.spannable = conversation.getSummaryText(itemView.context,
adapter.userColorNameManager, adapter.nameFirst)
if (conversation.is_outgoing) {
readIndicator.visibility = View.VISIBLE
readIndicator.setImageResource(R.drawable.ic_message_type_outgoing)

View File

@ -24,6 +24,7 @@ import android.view.View
import kotlinx.android.synthetic.main.list_item_message_conversation_text.view.*
import org.mariotaku.ktextension.empty
import org.mariotaku.ktextension.isNullOrEmpty
import org.mariotaku.ktextension.spannable
import org.mariotaku.messagebubbleview.library.MessageBubbleView
import org.mariotaku.twidere.R
import org.mariotaku.twidere.adapter.MessagesConversationAdapter
@ -92,7 +93,7 @@ class MessageViewHolder(itemView: View, adapter: MessagesConversationAdapter) :
}
text.text = SpannableStringBuilder.valueOf(message.text_unescaped).apply {
text.spannable = SpannableStringBuilder.valueOf(message.text_unescaped).apply {
message.spans?.applyTo(this)
adapter.linkify.applyAllLinks(this, message.account_key, layoutPosition.toLong(),
false, adapter.linkHighlightingStyle, true)

View File

@ -21,6 +21,7 @@ package org.mariotaku.twidere.view.holder.message
import android.view.View
import kotlinx.android.synthetic.main.list_item_message_conversation_notice.view.*
import org.mariotaku.ktextension.spannable
import org.mariotaku.twidere.R
import org.mariotaku.twidere.adapter.MessagesConversationAdapter
import org.mariotaku.twidere.extension.model.getSummaryText
@ -35,7 +36,7 @@ class NoticeSummaryEventViewHolder(itemView: View, adapter: MessagesConversation
override fun display(message: ParcelableMessage, showDate: Boolean) {
super.display(message, showDate)
text.text = message.getSummaryText(adapter.context, adapter.userColorNameManager,
text.spannable = message.getSummaryText(adapter.context, adapter.userColorNameManager,
adapter.conversation, adapter.nameFirst)
}

View File

@ -432,7 +432,7 @@
<string name="label_refresh_and_sync_service">刷新和同步服务</string>
<string name="label_refresh_service">刷新服务</string>
<string name="label_reply_name">回复 <xliff:g id="user_name">%s</xliff:g></string>
<string name="label_reply_name_text">引用 <xliff:g id="name">%1$s</xliff:g><xliff:g id="text">%2$s</xliff:g></string>
<string name="label_reply_name_text">回复 <xliff:g id="name">%1$s</xliff:g><xliff:g id="text">%2$s</xliff:g></string>
<string name="label_schedule_time_buffer_default">Buffer 默认</string>
<string name="label_send_at">发送于</string>
<string name="label_sensitive_content">敏感内容</string>

View File

@ -50,7 +50,7 @@
tools:tint="?android:textColorSecondary"
tools:visibility="visible"/>
<org.mariotaku.twidere.view.ActionIconThemedTextView
<org.mariotaku.twidere.view.FixedTextView
android:id="@+id/statusInfoLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"