diff --git a/twidere/build.gradle b/twidere/build.gradle index fe7c597d1..074ed9177 100644 --- a/twidere/build.gradle +++ b/twidere/build.gradle @@ -114,6 +114,7 @@ android { sourceSets.each { it.res.srcDirs += project.files("src/${it.name}/res-localized") it.res.srcDirs += project.files("src/${it.name}/res-svg2png") + it.res.srcDirs += project.files("src/${it.name}/res-preview") it.java.srcDirs += "src/${it.name}/kotlin" } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/adapter/ParcelableStatusesAdapter.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/adapter/ParcelableStatusesAdapter.kt index 647e66fcc..a6fa91cbb 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/adapter/ParcelableStatusesAdapter.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/adapter/ParcelableStatusesAdapter.kt @@ -50,6 +50,7 @@ import org.mariotaku.twidere.model.ItemCounts import org.mariotaku.twidere.model.ObjectId import org.mariotaku.twidere.model.ParcelableStatus import org.mariotaku.twidere.model.UserKey +import org.mariotaku.twidere.model.placeholder.ParcelableStatusPlaceholder import org.mariotaku.twidere.model.timeline.TimelineFilter import org.mariotaku.twidere.util.StatusAdapterLinkClickHandler import org.mariotaku.twidere.util.TwidereLinkify @@ -168,11 +169,11 @@ class ParcelableStatusesAdapter( } override fun isGapItem(position: Int): Boolean { - return getStatusInternal(false, false, position = position)?.is_gap == true + return getStatusInternal(false, false, position = position).is_gap } override fun getStatus(position: Int, raw: Boolean): ParcelableStatus { - return getStatusInternal(raw, position = position) ?: ParcelableStatusPlaceholder + return getStatusInternal(raw, position = position) } override fun getStatusCount(raw: Boolean): Int { @@ -290,7 +291,7 @@ class ParcelableStatusesAdapter( holder as IStatusViewHolder val countIndex: Int = getItemCountIndex(position) val status = getStatusInternal(loadAround = true, position = position, - countIndex = countIndex) ?: return + countIndex = countIndex) holder.display(status, displayInReplyTo = isShowInReplyTo, displayPinned = countIndex == ITEM_INDEX_PINNED_STATUS) } @@ -298,7 +299,7 @@ class ParcelableStatusesAdapter( (holder as TimelineFilterHeaderViewHolder).display(timelineFilter!!) } ITEM_VIEW_TYPE_GAP -> { - val status = getStatusInternal(loadAround = true, position = position) ?: return + val status = getStatusInternal(loadAround = true, position = position) val loading = gapLoadingIds.any { it.accountKey == status.account_key && it.id == status.id } (holder as GapViewHolder).display(loading) } @@ -396,7 +397,7 @@ class ParcelableStatusesAdapter( } private fun getStatusInternal(raw: Boolean = false, loadAround: Boolean = false, - position: Int, countIndex: Int = getItemCountIndex(position, raw)): ParcelableStatus? { + position: Int, countIndex: Int = getItemCountIndex(position, raw)): ParcelableStatus { when (countIndex) { ITEM_INDEX_PINNED_STATUS -> { return pinnedStatuses!![position - getItemStartPosition(ITEM_INDEX_PINNED_STATUS)] @@ -404,9 +405,9 @@ class ParcelableStatusesAdapter( ITEM_INDEX_STATUS -> { val dataPosition = position - statusStartIndex return if (loadAround) { - pagedStatusesHelper.getItem(dataPosition) + pagedStatusesHelper.getItem(dataPosition) ?: ParcelableStatusPlaceholder } else { - pagedStatusesHelper.currentList?.get(dataPosition) + pagedStatusesHelper.currentList?.get(dataPosition) ?: ParcelableStatusPlaceholder } } } @@ -422,18 +423,6 @@ class ParcelableStatusesAdapter( itemCounts[ITEM_INDEX_LOAD_END_INDICATOR] = if (LoadMorePosition.END in loadMoreIndicatorPosition) 1 else 0 } - object ParcelableStatusPlaceholder : ParcelableStatus() { - init { - id = "none" - account_key = UserKey.INVALID - user_key = UserKey.INVALID - } - - override fun hashCode(): Int { - return -1 - } - } - companion object { const val VIEW_TYPE_STATUS = 2 const val VIEW_TYPE_EMPTY = 3 diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/model/placeholder/ParcelableStatusPlaceholder.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/model/placeholder/ParcelableStatusPlaceholder.kt new file mode 100644 index 000000000..92666bb34 --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/model/placeholder/ParcelableStatusPlaceholder.kt @@ -0,0 +1,35 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.mariotaku.twidere.model.placeholder + +import org.mariotaku.twidere.model.ParcelableStatus +import org.mariotaku.twidere.model.UserKey + +object ParcelableStatusPlaceholder : ParcelableStatus() { + init { + id = "none" + account_key = UserKey.INVALID + user_key = UserKey.INVALID + } + + override fun hashCode(): Int { + return -1 + } +} \ No newline at end of file diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/preference/CardPreviewPreference.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/preference/CardPreviewPreference.kt index cd6676b8d..33f948e6e 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/preference/CardPreviewPreference.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/preference/CardPreviewPreference.kt @@ -69,7 +69,7 @@ class CardPreviewPreference( } this.holder?.let { it.setupViewOptions() - it.displaySampleStatus() + it.preview() } super.onBindViewHolder(holder) } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/text/TwidereClickableSpan.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/text/TwidereClickableSpan.kt index 5fa8c94cb..505b117ec 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/text/TwidereClickableSpan.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/text/TwidereClickableSpan.kt @@ -2,14 +2,11 @@ package org.mariotaku.twidere.text import android.text.TextPaint import android.text.style.ClickableSpan +import android.view.View import org.mariotaku.ktextension.contains import org.mariotaku.twidere.constant.SharedPreferenceConstants -/** - * Created by Mariotaku on 2017/5/21. - */ - -abstract class TwidereClickableSpan(val highlightStyle: Int): ClickableSpan() { +class TwidereClickableSpan(val highlightStyle: Int, val callback: () -> Unit) : ClickableSpan() { override fun updateDrawState(ds: TextPaint) { if (SharedPreferenceConstants.VALUE_LINK_HIGHLIGHT_OPTION_CODE_UNDERLINE in highlightStyle) { @@ -19,4 +16,8 @@ abstract class TwidereClickableSpan(val highlightStyle: Int): ClickableSpan() { ds.color = ds.linkColor } } + + override fun onClick(widget: View?) { + callback() + } } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/text/style/PlaceholderLineSpan.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/text/style/PlaceholderLineSpan.kt new file mode 100644 index 000000000..655bc1c63 --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/text/style/PlaceholderLineSpan.kt @@ -0,0 +1,58 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.mariotaku.twidere.text.style + +import android.graphics.Canvas +import android.graphics.Paint +import android.text.style.ReplacementSpan + +class PlaceholderLineSpan(val width: Float, val widthRelativeToChar: Boolean = false) : ReplacementSpan() { + + private val fontMetrics = Paint.FontMetrics() + + override fun getSize(paint: Paint, text: CharSequence?, start: Int, end: Int, fm: Paint.FontMetricsInt?): Int { + paint.getFontMetrics(fontMetrics) + if (fm != null) { + paint.getFontMetricsInt(fm) + } + return if (widthRelativeToChar) { + (width * paint.textSize).toInt() + } else { + 100 + } + } + + override fun draw(canvas: Canvas, text: CharSequence?, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint) { + val width = if (widthRelativeToChar) { + width * paint.textSize + } else { + (canvas.width - x) * width + } + val paintAlphaBackup = paint.alpha + paint.alpha = placeholderAlpha + canvas.drawRect(x, top + fontMetrics.leading, x + width, bottom - fontMetrics.descent, + paint) + paint.alpha = paintAlphaBackup + } + + companion object { + const val placeholderAlpha = 0x20 + } +} diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/view/ColorLabelConstraintLayout.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/view/ColorLabelConstraintLayout.kt new file mode 100644 index 000000000..95b5caa12 --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/view/ColorLabelConstraintLayout.kt @@ -0,0 +1,71 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.mariotaku.twidere.view + +import android.content.Context +import android.graphics.Canvas +import android.support.constraint.ConstraintLayout +import android.util.AttributeSet + +import org.mariotaku.twidere.view.iface.IColorLabelView + +class ColorLabelConstraintLayout(context: Context, attrs: AttributeSet? = null) : ConstraintLayout(context, attrs), IColorLabelView { + + private val helper = IColorLabelView.Helper(this, context, attrs, 0) + + override fun drawBackground(color: Int) { + helper.drawBackground(color) + } + + override fun drawBottom(vararg colors: Int) { + helper.drawBottom(colors) + } + + override fun drawEnd(vararg colors: Int) { + helper.drawEnd(colors) + } + + override fun drawLabel(start: IntArray, end: IntArray, top: IntArray, bottom: IntArray, background: Int) { + helper.drawLabel(start, end, top, bottom, background) + } + + override fun drawStart(vararg colors: Int) { + helper.drawStart(colors) + } + + override fun drawTop(vararg colors: Int) { + helper.drawTop(colors) + } + + override fun isPaddingIgnored(): Boolean { + return helper.isPaddingIgnored + } + + override fun setIgnorePadding(ignorePadding: Boolean) { + helper.setIgnorePadding(ignorePadding) + } + + override fun dispatchDraw(canvas: Canvas) { + helper.dispatchDrawBackground(canvas) + super.dispatchDraw(canvas) + helper.dispatchDrawLabels(canvas) + } + +} diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/view/ShortTimeView.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/view/ShortTimeView.kt index 0adac440e..ad0ec0645 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/view/ShortTimeView.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/view/ShortTimeView.kt @@ -24,10 +24,13 @@ import android.os.Handler import android.os.HandlerThread import android.os.SystemClock import android.support.v7.widget.AppCompatTextView +import android.text.Spannable +import android.text.SpannableString import android.text.format.DateUtils import android.util.AttributeSet import org.mariotaku.twidere.Constants import org.mariotaku.twidere.R +import org.mariotaku.twidere.text.style.PlaceholderLineSpan import org.mariotaku.twidere.util.Utils.formatSameDayTime import java.lang.ref.WeakReference import java.util.concurrent.TimeUnit @@ -40,6 +43,8 @@ class ShortTimeView( private val ticker = TickerRunnable(this) private val invalidateTimeRunnable = Runnable { + val time = this.time + if (time < 0) return@Runnable val label = getTimeLabel(context, time, showAbsoluteTime) post { setTextIfChanged(label) @@ -52,7 +57,7 @@ class ShortTimeView( invalidateTime() } - var time: Long = 0 + var time: Long = -1 set(value) { field = value invalidateTime() @@ -69,7 +74,11 @@ class ShortTimeView( } private fun invalidateTime() { - updateHandler.post(invalidateTimeRunnable) + if (time == PLACEHOLDER) { + text = placeholderText + } else { + updateHandler.post(invalidateTimeRunnable) + } } private fun setTextIfChanged(text: CharSequence?) { @@ -93,6 +102,13 @@ class ShortTimeView( companion object { + const val INVALID: Long = -1 + const val PLACEHOLDER: Long = -2 + + private val placeholderText = SpannableString(" ").apply { + setSpan(PlaceholderLineSpan(3.5f, true), 0, length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } + private const val TICKER_DURATION = 5000L private val ONE_MINUTE_MILLIS = TimeUnit.MINUTES.toMillis(1) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/view/TwoLineTextView.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/view/TwoLineTextView.kt index 60700dbb7..4b18a6e05 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/view/TwoLineTextView.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/view/TwoLineTextView.kt @@ -23,14 +23,13 @@ import android.content.Context import android.content.res.Resources import android.support.annotation.Dimension import android.support.v4.text.BidiFormatter -import android.text.SpannableStringBuilder -import android.text.Spanned -import android.text.TextUtils +import android.text.* import android.text.style.TextAppearanceSpan import android.util.AttributeSet import android.util.TypedValue import org.mariotaku.twidere.R import org.mariotaku.twidere.model.theme.TextAppearance +import org.mariotaku.twidere.text.style.PlaceholderLineSpan open class TwoLineTextView(context: Context, attrs: AttributeSet? = null) : FixedTextView(context, attrs) { @@ -46,6 +45,8 @@ open class TwoLineTextView(context: Context, attrs: AttributeSet? = null) : Fixe var primaryText: CharSequence? = null var secondaryText: CharSequence? = null + var placeholder: Boolean = false + protected open val displayPrimaryText: CharSequence? get() = primaryText @@ -93,6 +94,10 @@ open class TwoLineTextView(context: Context, attrs: AttributeSet? = null) : Fixe } fun updateText(formatter: BidiFormatter? = null) { + if (placeholder) { + text = placeholderText + return + } val sb = SpannableStringBuilder() val primaryText = displayPrimaryText val secondaryText = displaySecondaryText @@ -131,4 +136,10 @@ open class TwoLineTextView(context: Context, attrs: AttributeSet? = null) : Fixe return TypedValue.applyDimension(unit, size, r.displayMetrics) } + companion object { + private val placeholderText = SpannableString(" ").apply { + setSpan(PlaceholderLineSpan(0.6f), 0, length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } + } + } \ No newline at end of file diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/iface/IStatusViewHolder.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/iface/IStatusViewHolder.kt index 4e9b30b75..1c51cb919 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/iface/IStatusViewHolder.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/iface/IStatusViewHolder.kt @@ -30,9 +30,6 @@ import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.view.CardMediaContainer import org.mariotaku.twidere.view.holder.TimelineFilterHeaderViewHolder -/** - * Created by mariotaku on 15/10/26. - */ interface IStatusViewHolder : CardMediaContainer.OnMediaClickListener { fun display(status: ParcelableStatus, displayInReplyTo: Boolean = true, diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/status/StatusViewHolder.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/status/StatusViewHolder.kt index cb6bb943e..95bf59560 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/status/StatusViewHolder.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/status/StatusViewHolder.kt @@ -48,22 +48,26 @@ import org.mariotaku.twidere.constant.SharedPreferenceConstants.VALUE_LINK_HIGHL import org.mariotaku.twidere.extension.loadProfileImage import org.mariotaku.twidere.extension.model.* import org.mariotaku.twidere.extension.setVisible +import org.mariotaku.twidere.extension.text.appendCompat import org.mariotaku.twidere.graphic.like.LikeAnimationDrawable import org.mariotaku.twidere.model.ParcelableLocation import org.mariotaku.twidere.model.ParcelableMedia import org.mariotaku.twidere.model.ParcelableStatus import org.mariotaku.twidere.model.UserKey +import org.mariotaku.twidere.model.placeholder.ParcelableStatusPlaceholder import org.mariotaku.twidere.task.CreateFavoriteTask import org.mariotaku.twidere.task.DestroyFavoriteTask import org.mariotaku.twidere.task.DestroyStatusTask import org.mariotaku.twidere.task.RetweetStatusTask import org.mariotaku.twidere.text.TwidereClickableSpan +import org.mariotaku.twidere.text.style.PlaceholderLineSpan import org.mariotaku.twidere.util.HtmlEscapeHelper.toPlainText import org.mariotaku.twidere.util.HtmlSpanBuilder import org.mariotaku.twidere.util.ThemeUtils import org.mariotaku.twidere.util.UnitConvertUtils import org.mariotaku.twidere.util.Utils.getUserTypeIconRes import org.mariotaku.twidere.view.ShapedImageView +import org.mariotaku.twidere.view.ShortTimeView import org.mariotaku.twidere.view.holder.iface.IStatusViewHolder /** @@ -71,35 +75,40 @@ import org.mariotaku.twidere.view.holder.iface.IStatusViewHolder */ class StatusViewHolder(private val adapter: IStatusesAdapter, itemView: View) : ViewHolder(itemView), IStatusViewHolder { - override val profileImageView: ShapedImageView by lazy { itemView.profileImage } - override val profileTypeView: ImageView by lazy { itemView.profileType } + override val profileImageView: ShapedImageView = itemView.profileImage + override val profileTypeView: ImageView = itemView.profileType - private val itemContent by lazy { itemView.itemContent } - private val mediaPreview by lazy { itemView.mediaPreview } - private val statusContentUpperSpace by lazy { itemView.statusContentUpperSpace } - private val summaryView by lazy { itemView.summary } - private val textView by lazy { itemView.text } - private val nameView by lazy { itemView.name } - private val itemMenu by lazy { itemView.itemMenu } - private val statusInfoLabel by lazy { itemView.statusInfoLabel } - private val statusInfoIcon by lazy { itemView.statusInfoIcon } - private val quotedNameView by lazy { itemView.quotedName } - private val timeView by lazy { itemView.time } - private val quotedView by lazy { itemView.quotedView } - private val quotedTextView by lazy { itemView.quotedText } - private val actionButtons by lazy { itemView.actionButtons } - private val mediaLabel by lazy { itemView.mediaLabel } - private val quotedMediaLabel by lazy { itemView.quotedMediaLabel } - private val statusContentLowerSpace by lazy { itemView.statusContentLowerSpace } - private val quotedMediaPreview by lazy { itemView.quotedMediaPreview } - private val replyButton by lazy { itemView.reply } - private val retweetButton by lazy { itemView.retweet } - private val favoriteButton by lazy { itemView.favorite } + private val itemContent = itemView.itemContent + private val mediaPreview = itemView.mediaPreview + private val summaryView = itemView.summary + private val textView = itemView.text + private val nameView = itemView.name + private val itemMenu = itemView.itemMenu + private val statusInfoLabel = itemView.statusInfoLabel + private val statusInfoIcon = itemView.statusInfoIcon + private val quotedNameView = itemView.quotedName + private val timeView = itemView.time + private val quotedView = itemView.quotedView + private val quotedTextView = itemView.quotedText + private val mediaLabel = itemView.mediaLabel + private val quotedMediaLabel = itemView.quotedMediaLabel + private val quotedMediaPreview = itemView.quotedMediaPreview + private val replyButton = itemView.reply + private val retweetButton = itemView.retweet + private val favoriteButton = itemView.favorite private val eventHandler = EventHandler() private var statusClickListener: IStatusViewHolder.StatusClickListener? = null + private val toggleFullTextSpan = TwidereClickableSpan(adapter.linkHighlightingStyle) { + if (adapter.isFullTextVisible(layoutPosition)) { + hideFullText() + } else { + showFullText() + } + } + init { if (adapter.mediaPreviewEnabled) { @@ -111,11 +120,9 @@ class StatusViewHolder(private val adapter: IStatusesAdapter, itemView: View) : } - - fun displaySampleStatus() { + fun preview() { val profileImageEnabled = adapter.profileImageEnabled profileImageView.visibility = if (profileImageEnabled) View.VISIBLE else View.GONE - statusContentUpperSpace.visibility = View.VISIBLE adapter.requestManager.loadProfileImage(itemView.context, R.drawable.ic_profile_image_twidere, adapter.profileImageStyle, profileImageView.cornerRadius, @@ -141,15 +148,57 @@ class StatusViewHolder(private val adapter: IStatusesAdapter, itemView: View) : mediaPreview.visibility = View.GONE mediaLabel.visibility = View.VISIBLE } - actionButtons.visibility = if (showCardActions) View.VISIBLE else View.GONE - itemMenu.visibility = if (showCardActions) View.VISIBLE else View.GONE - statusContentLowerSpace.visibility = if (showCardActions) View.GONE else View.VISIBLE - quotedMediaPreview.visibility = View.GONE - quotedMediaLabel.visibility = View.GONE + + replyButton.setVisible(showCardActions) + retweetButton.setVisible(showCardActions) + favoriteButton.setVisible(showCardActions) + itemMenu.setVisible(showCardActions) + + quotedView.visibility = View.GONE + mediaPreview.displayMedia(R.drawable.featured_graphics) } + fun placeholder() { + val context = itemView.context + val requestManager = adapter.requestManager + requestManager.loadProfileImage(context, R.drawable.ic_profile_image_placeholder, adapter.profileImageStyle, + profileImageView.cornerRadius, profileImageView.cornerRadiusRatio).into(profileImageView) + + timeView.time = ShortTimeView.PLACEHOLDER + textView.spannable = placeholderText + nameView.placeholder = true + nameView.updateText() + + val actionButtonsAlpha = PlaceholderLineSpan.placeholderAlpha / 255f + + replyButton.alpha = actionButtonsAlpha + retweetButton.alpha = actionButtonsAlpha + favoriteButton.alpha = actionButtonsAlpha + itemMenu.alpha = actionButtonsAlpha + + replyButton.isActivated = false + retweetButton.isActivated = false + favoriteButton.isActivated = false + + replyButton.text = null + retweetButton.text = null + favoriteButton.text = null + + profileTypeView.visibility = View.GONE + summaryView.visibility = View.GONE + mediaLabel.visibility = View.GONE + mediaPreview.visibility = View.GONE + quotedView.visibility = View.GONE + statusInfoIcon.visibility = View.GONE + statusInfoLabel.visibility = View.GONE + } + override fun display(status: ParcelableStatus, displayInReplyTo: Boolean, displayPinned: Boolean) { + if (status === ParcelableStatusPlaceholder) { + placeholder() + return + } val context = itemView.context val requestManager = adapter.requestManager val linkify = adapter.twidereLinkify @@ -157,9 +206,15 @@ class StatusViewHolder(private val adapter: IStatusesAdapter, itemView: View) : val colorNameManager = adapter.userColorNameManager val showCardActions = isCardActionsShown - actionButtons.visibility = if (showCardActions) View.VISIBLE else View.GONE - itemMenu.visibility = if (showCardActions) View.VISIBLE else View.GONE - statusContentLowerSpace.visibility = if (showCardActions) View.GONE else View.VISIBLE + replyButton.alpha = 1f + retweetButton.alpha = 1f + favoriteButton.alpha = 1f + itemMenu.alpha = 1f + + replyButton.setVisible(showCardActions) + retweetButton.setVisible(showCardActions) + favoriteButton.setVisible(showCardActions) + itemMenu.setVisible(showCardActions) val replyCount = status.reply_count val retweetCount = status.retweet_count @@ -170,8 +225,6 @@ class StatusViewHolder(private val adapter: IStatusesAdapter, itemView: View) : statusInfoIcon.setImageResource(R.drawable.ic_activity_action_pinned) statusInfoLabel.visibility = View.VISIBLE statusInfoIcon.visibility = View.VISIBLE - - statusContentUpperSpace.visibility = View.GONE } 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!!) @@ -179,8 +232,6 @@ class StatusViewHolder(private val adapter: IStatusesAdapter, itemView: View) : statusInfoIcon.setImageResource(R.drawable.ic_activity_action_retweet) statusInfoLabel.visibility = View.VISIBLE statusInfoIcon.visibility = View.VISIBLE - - statusContentUpperSpace.visibility = View.GONE } else if (status.in_reply_to_status_id != null && status.in_reply_to_user_key != null && displayInReplyTo) { if (status.in_reply_to_name != null && status.in_reply_to_screen_name != null) { val inReplyTo = colorNameManager.getDisplayName(status.in_reply_to_user_key!!, @@ -192,13 +243,9 @@ class StatusViewHolder(private val adapter: IStatusesAdapter, itemView: View) : statusInfoIcon.setImageResource(R.drawable.ic_activity_action_reply) statusInfoLabel.visibility = View.VISIBLE statusInfoIcon.visibility = View.VISIBLE - - statusContentUpperSpace.visibility = View.GONE } else { statusInfoLabel.visibility = View.GONE statusInfoIcon.visibility = View.GONE - - statusContentUpperSpace.visibility = View.VISIBLE } val skipLinksInText = status.extras?.support_entities ?: false @@ -296,6 +343,7 @@ class StatusViewHolder(private val adapter: IStatusesAdapter, itemView: View) : status.timestamp } + nameView.placeholder = false nameView.name = colorNameManager.getUserNickname(status.user_key, status.user_name) nameView.screenName = "@${status.user_acct}" @@ -356,11 +404,7 @@ class StatusViewHolder(private val adapter: IStatusesAdapter, itemView: View) : val displayEnd: Int if (!summaryView.empty && !isFullTextVisible) { text = SpannableStringBuilder.valueOf(context.getString(R.string.label_status_show_more)).apply { - setSpan(object : TwidereClickableSpan(adapter.linkHighlightingStyle) { - override fun onClick(widget: View?) { - showFullText() - } - }, 0, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + setSpan(toggleFullTextSpan, 0, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) } displayEnd = -1 } else if (adapter.linkHighlightingStyle != VALUE_LINK_HIGHLIGHT_OPTION_CODE_NONE) { @@ -665,8 +709,8 @@ class StatusViewHolder(private val adapter: IStatusesAdapter, itemView: View) : } private fun TextView.setLabelIcon(@DrawableRes icon: Int) { - TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(this, icon, 0, - 0, 0) + TextViewCompat.setCompoundDrawablesRelative(this, ContextCompat.getDrawable(context, icon), + null, null, null) } private val Array.type: Int @@ -744,6 +788,16 @@ class StatusViewHolder(private val adapter: IStatusesAdapter, itemView: View) : private val videoTypes = intArrayOf(ParcelableMedia.Type.VIDEO, ParcelableMedia.Type.ANIMATED_GIF, ParcelableMedia.Type.EXTERNAL_PLAYER) + private val placeholderText = SpannableStringBuilder().apply { + appendCompat(" ", PlaceholderLineSpan(1f), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + appendln() + appendCompat(" ", PlaceholderLineSpan(1f), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + appendln() + appendCompat(" ", PlaceholderLineSpan(1f), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + appendln() + appendCompat(" ", PlaceholderLineSpan(.5f), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + } + fun isRetweetIconActivated(status: ParcelableStatus): Boolean { return !DestroyStatusTask.isRunning(status.account_key, status.my_retweet_id) && (RetweetStatusTask.isRunning(status.account_key, status.id) || diff --git a/twidere/src/main/res/drawable/ic_profile_image_placeholder.xml b/twidere/src/main/res/drawable/ic_profile_image_placeholder.xml new file mode 100644 index 000000000..b7e0eb15d --- /dev/null +++ b/twidere/src/main/res/drawable/ic_profile_image_placeholder.xml @@ -0,0 +1,28 @@ + + + + + + + + \ No newline at end of file diff --git a/twidere/src/main/res/layout/list_item_status.xml b/twidere/src/main/res/layout/list_item_status.xml index d22790547..06091446c 100644 --- a/twidere/src/main/res/layout/list_item_status.xml +++ b/twidere/src/main/res/layout/list_item_status.xml @@ -1,22 +1,22 @@ - @@ -73,15 +67,9 @@ style="?profileImageStyle" android:layout_width="@dimen/icon_size_status_profile_image" android:layout_height="@dimen/icon_size_status_profile_image" - android:layout_alignParentLeft="true" - android:layout_alignParentStart="true" - android:layout_alignTop="@+id/statusContent" - android:layout_alignWithParentIfMissing="true" - android:layout_below="@+id/statusInfoLabel" - android:layout_marginBottom="@dimen/element_spacing_normal" - android:layout_marginEnd="@dimen/element_spacing_normal" - android:layout_marginRight="@dimen/element_spacing_normal" android:scaleType="centerCrop" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="@+id/name" tools:ignore="ContentDescription" tools:src="@drawable/ic_profile_image_twidere" tools:visibility="visible"/> @@ -90,318 +78,258 @@ android:id="@+id/profileType" android:layout_width="@dimen/icon_size_profile_type" android:layout_height="@dimen/icon_size_profile_type" - android:layout_alignBottom="@+id/profileImage" - android:layout_alignEnd="@+id/profileImage" - android:layout_alignRight="@+id/profileImage" - android:layout_marginBottom="@dimen/element_spacing_minus_small" - android:layout_marginEnd="@dimen/element_spacing_minus_small" - android:layout_marginRight="@dimen/element_spacing_minus_small" android:scaleType="fitCenter" + app:layout_constraintBottom_toBottomOf="@+id/profileImage" + app:layout_constraintEnd_toEndOf="@+id/profileImage" + tools:src="@drawable/ic_user_type_verified" tools:visibility="visible"/> - - - + android:layout_marginEnd="@dimen/element_spacing_small" + android:layout_marginLeft="@dimen/element_spacing_normal" + android:layout_marginRight="@dimen/element_spacing_small" + android:layout_marginStart="@dimen/element_spacing_normal" + android:ellipsize="end" + android:maxLines="1" + app:layout_constraintEnd_toStartOf="@+id/time" + app:layout_constraintStart_toEndOf="@+id/profileImage" + app:layout_constraintTop_toBottomOf="@+id/statusInfoLabel" + app:layout_goneMarginStart="0dp" + app:layout_goneMarginTop="@dimen/element_spacing_normal" + app:tltvPrimaryTextAppearance="?android:textAppearanceSmall" + app:tltvPrimaryTextColor="?android:textColorPrimary" + app:tltvPrimaryTextStyle="bold" + app:tltvSecondaryTextAppearance="?android:textAppearanceSmall" + app:tltvSecondaryTextColor="?android:textColorSecondary" + tools:tltvPrimaryText="Name" + tools:tltvSecondaryText="\@screenname"/> + + + + + + + + + + + + + + + + + - - - - + tools:text="@string/label_media" + tools:visibility="visible"/> + tools:visibility="gone"/> + - - - + - + - - - - - - - - - - - - - - - - - - - - - - + + app:tint="?android:textColorSecondary"/> - \ No newline at end of file + + \ No newline at end of file