package jp.juggler.subwaytooter import android.content.Context import android.graphics.Typeface import android.os.SystemClock import android.support.v4.content.ContextCompat import android.support.v4.view.ViewCompat import android.support.v7.app.AlertDialog import android.support.v7.widget.RecyclerView import android.text.Spannable import android.text.SpannableString import android.text.SpannableStringBuilder import android.text.TextUtils import android.util.TypedValue import android.view.Gravity import android.view.View import android.view.ViewGroup import android.widget.* import com.google.android.flexbox.FlexWrap import com.google.android.flexbox.FlexboxLayout import com.google.android.flexbox.JustifyContent import jp.juggler.subwaytooter.Styler.defaultColorIcon import jp.juggler.subwaytooter.action.* import jp.juggler.subwaytooter.api.* import jp.juggler.subwaytooter.api.entity.* import jp.juggler.subwaytooter.dialog.ActionsDialog import jp.juggler.subwaytooter.dialog.DlgConfirm import jp.juggler.subwaytooter.drawable.PreviewCardBorder import jp.juggler.subwaytooter.span.MyClickableSpan import jp.juggler.subwaytooter.table.* import jp.juggler.subwaytooter.util.* import jp.juggler.subwaytooter.view.* import jp.juggler.util.* import org.jetbrains.anko.* import org.json.JSONObject import kotlin.math.max internal class ItemViewHolder( val activity : ActMain ) : View.OnClickListener, View.OnLongClickListener { companion object { private val log = LogCategory("ItemViewHolder") var toot_color_unlisted : Int = 0 var toot_color_follower : Int = 0 var toot_color_direct_user : Int = 0 var toot_color_direct_me : Int = 0 } val viewRoot : View private var bSimpleList : Boolean = false lateinit var column : Column internal lateinit var list_adapter : ItemListAdapter private lateinit var llBoosted : View private lateinit var ivBoosted : ImageView private lateinit var tvBoosted : TextView private lateinit var tvBoostedAcct : TextView private lateinit var tvBoostedTime : TextView private lateinit var llReply : View private lateinit var ivReply : ImageView private lateinit var tvReply : TextView private lateinit var llFollow : View private lateinit var ivFollow : MyNetworkImageView private lateinit var tvFollowerName : TextView private lateinit var tvFollowerAcct : TextView private lateinit var btnFollow : ImageButton private lateinit var ivFollowedBy : ImageView private lateinit var llStatus : View private lateinit var ivThumbnail : MyNetworkImageView private lateinit var tvName : TextView private lateinit var tvTime : TextView private lateinit var tvAcct : TextView private lateinit var llContentWarning : View private lateinit var tvContentWarning : MyTextView private lateinit var btnContentWarning : Button private lateinit var llContents : View private lateinit var tvMentions : MyTextView internal lateinit var tvContent : MyTextView private lateinit var flMedia : View private lateinit var llMedia : View private lateinit var btnShowMedia : TextView private lateinit var ivMedia1 : MyNetworkImageView private lateinit var ivMedia2 : MyNetworkImageView private lateinit var ivMedia3 : MyNetworkImageView private lateinit var ivMedia4 : MyNetworkImageView private lateinit var btnHideMedia : ImageButton private lateinit var statusButtonsViewHolder : StatusButtonsViewHolder private lateinit var llButtonBar : View private lateinit var llSearchTag : View private lateinit var btnSearchTag : Button private lateinit var llTrendTag : View private lateinit var tvTrendTagName : TextView private lateinit var tvTrendTagDesc : TextView private lateinit var tvTrendTagCount : TextView private lateinit var cvTrendTagHistory : TrendTagHistoryView private lateinit var llList : View private lateinit var btnListTL : Button private lateinit var btnListMore : ImageButton private lateinit var llFollowRequest : View private lateinit var btnFollowRequestAccept : View private lateinit var btnFollowRequestDeny : View private lateinit var llFilter : View private lateinit var tvFilterPhrase : TextView private lateinit var tvFilterDetail : TextView private lateinit var tvMediaDescription : TextView private lateinit var llCardOuter : View private lateinit var tvCardText : MyTextView private lateinit var ivCardImage : MyNetworkImageView private lateinit var llExtra : LinearLayout private lateinit var llConversationIcons : View private lateinit var ivConversationIcon1 : MyNetworkImageView private lateinit var ivConversationIcon2 : MyNetworkImageView private lateinit var ivConversationIcon3 : MyNetworkImageView private lateinit var ivConversationIcon4 : MyNetworkImageView private lateinit var tvConversationIconsMore : TextView private lateinit var tvConversationParticipants : TextView private lateinit var tvApplication : TextView private lateinit var tvMessageHolder : TextView private lateinit var llInstanceTicker : View private lateinit var ivInstanceTicker : MyNetworkImageView private lateinit var tvInstanceTicker : TextView private lateinit var access_info : SavedAccount private var buttons_for_status : StatusButtons? = null private var item : TimelineItem? = null private var status_showing : TootStatus? = null private var status_reply : TootStatus? = null private var status_account : TootAccountRef? = null private var boost_account : TootAccountRef? = null private var follow_account : TootAccountRef? = null private var boost_time : Long = 0L private var content_color : Int = 0 private var acct_color : Int = 0 private val boost_invalidator : NetworkEmojiInvalidator private val reply_invalidator : NetworkEmojiInvalidator private val follow_invalidator : NetworkEmojiInvalidator private val name_invalidator : NetworkEmojiInvalidator private val content_invalidator : NetworkEmojiInvalidator private val spoiler_invalidator : NetworkEmojiInvalidator private val extra_invalidator_list = ArrayList() init { this.viewRoot = inflate(activity) btnListTL.setOnClickListener(this) btnListMore.setOnClickListener(this) btnSearchTag.setOnClickListener(this) btnSearchTag.setOnLongClickListener(this) btnContentWarning.setOnClickListener(this) btnShowMedia.setOnClickListener(this) ivMedia1.setOnClickListener(this) ivMedia2.setOnClickListener(this) ivMedia3.setOnClickListener(this) ivMedia4.setOnClickListener(this) btnFollow.setOnClickListener(this) btnFollow.setOnLongClickListener(this) ivCardImage.setOnClickListener(this) ivCardImage.setOnLongClickListener(this) ivThumbnail.setOnClickListener(this) llBoosted.setOnClickListener(this) llBoosted.setOnLongClickListener(this) llReply.setOnClickListener(this) llReply.setOnLongClickListener(this) llFollow.setOnClickListener(this) llFollow.setOnLongClickListener(this) llConversationIcons.setOnLongClickListener(this) btnFollow.setOnClickListener(this) btnFollowRequestAccept.setOnClickListener(this) btnFollowRequestDeny.setOnClickListener(this) // ロングタップ ivThumbnail.setOnLongClickListener(this) // tvContent.movementMethod = MyLinkMovementMethod tvMentions.movementMethod = MyLinkMovementMethod tvContentWarning.movementMethod = MyLinkMovementMethod tvMediaDescription.movementMethod = MyLinkMovementMethod tvCardText.movementMethod = MyLinkMovementMethod btnHideMedia.setOnClickListener(this) llTrendTag.setOnClickListener(this) llTrendTag.setOnLongClickListener(this) llFilter.setOnClickListener(this) var f : Float f = activity.timeline_font_size_sp if(! f.isNaN()) { tvFollowerName.textSize = f tvName.textSize = f tvMentions.textSize = f tvContentWarning.textSize = f tvContent.textSize = f btnShowMedia.textSize = f tvApplication.textSize = f tvMessageHolder.textSize = f btnListTL.textSize = f tvTrendTagName.textSize = f tvTrendTagCount.textSize = f tvFilterPhrase.textSize = f tvMediaDescription.textSize = f tvCardText.textSize = f tvConversationIconsMore.textSize = f tvConversationParticipants.textSize = f } f = activity.notification_tl_font_size_sp if(! f.isNaN()) { tvBoosted.textSize = f tvReply.textSize = f } f = activity.acct_font_size_sp if(! f.isNaN()) { tvBoostedAcct.textSize = f tvBoostedTime.textSize = f tvFollowerAcct.textSize = f tvAcct.textSize = f tvTime.textSize = f tvTrendTagDesc.textSize = f tvFilterDetail.textSize = f } var s = activity.avatarIconSize ivThumbnail.layoutParams.height = s ivThumbnail.layoutParams.width = s ivFollow.layoutParams.width = s ivBoosted.layoutParams.width = s s = ActMain.replyIconSize + (activity.density * 8).toInt() ivReply.layoutParams.width = s ivReply.layoutParams.height = s s = activity.notificationTlIconSize ivBoosted.layoutParams.height = s this.content_invalidator = NetworkEmojiInvalidator(activity.handler, tvContent) this.spoiler_invalidator = NetworkEmojiInvalidator(activity.handler, tvContentWarning) this.boost_invalidator = NetworkEmojiInvalidator(activity.handler, tvBoosted) this.reply_invalidator = NetworkEmojiInvalidator(activity.handler, tvReply) this.follow_invalidator = NetworkEmojiInvalidator(activity.handler, tvFollowerName) this.name_invalidator = NetworkEmojiInvalidator(activity.handler, tvName) val cardBackground = llCardOuter.background if(cardBackground is PreviewCardBorder) { val density = activity.density cardBackground.round = (density * 8f) cardBackground.width = (density * 1f) } } fun onViewRecycled() { } fun bind( list_adapter : ItemListAdapter, column : Column, bSimpleList : Boolean, item : TimelineItem ) { val b = Benchmark(log, "Item-bind", 40L) this.list_adapter = list_adapter this.column = column this.bSimpleList = bSimpleList this.access_info = column.access_info val font_bold = ActMain.timeline_font_bold val font_normal = ActMain.timeline_font viewRoot.scan { v -> try { when(v) { // ボタンは太字なので触らない is CountImageButton -> { } // ボタンは太字なので触らない is Button -> { } is TextView -> v.typeface = when { v === tvName || v === tvFollowerName || v === tvBoosted || v === tvReply || v === tvTrendTagCount || v === tvTrendTagName || v === tvConversationIconsMore || v === tvConversationParticipants || v === tvFilterPhrase -> font_bold else -> font_normal } } } catch(ex : Throwable) { log.trace(ex) } } if(bSimpleList) { viewRoot.setOnTouchListener { _, ev -> // ポップアップを閉じた時にクリックでリストを触ったことになってしまう不具合の回避 val now = SystemClock.elapsedRealtime() // ポップアップを閉じた直後はタッチダウンを無視する if(now - StatusButtonsPopup.last_popup_close >= 30L) { false } else { val action = ev.action log.d("onTouchEvent action=$action") true } } viewRoot.setOnClickListener { viewClicked -> activity.closeListItemPopup() status_showing?.let { status -> val popup = StatusButtonsPopup(activity, column, bSimpleList, this@ItemViewHolder) activity.listItemPopup = popup popup.show( list_adapter.columnVh.listView, viewClicked, status, item as? TootNotification ) } } llButtonBar.visibility = View.GONE this.buttons_for_status = null } else { viewRoot.isClickable = false llButtonBar.visibility = View.VISIBLE this.buttons_for_status = StatusButtons( activity, column, false, statusButtonsViewHolder, this ) } this.status_showing = null this.status_reply = null this.status_account = null this.boost_account = null this.follow_account = null this.boost_time = 0L this.viewRoot.setBackgroundColor(0) this.boostedAction = defaultBoostedAction llInstanceTicker.visibility = View.GONE llBoosted.visibility = View.GONE llReply.visibility = View.GONE llFollow.visibility = View.GONE llStatus.visibility = View.GONE llSearchTag.visibility = View.GONE llList.visibility = View.GONE llFollowRequest.visibility = View.GONE tvMessageHolder.visibility = View.GONE llTrendTag.visibility = View.GONE llFilter.visibility = View.GONE tvMediaDescription.visibility = View.GONE llCardOuter.visibility = View.GONE tvCardText.visibility = View.GONE ivCardImage.visibility = View.GONE llConversationIcons.visibility = View.GONE removeExtraView() var c : Int c = column.getContentColor() this.content_color = c tvBoosted.setTextColor(c) tvReply.setTextColor(c) tvFollowerName.setTextColor(c) tvName.setTextColor(c) tvMentions.setTextColor(c) tvContentWarning.setTextColor(c) tvContent.setTextColor(c) //NSFWは文字色固定 btnShowMedia.setTextColor( c ); tvApplication.setTextColor(c) tvMessageHolder.setTextColor(c) tvTrendTagName.setTextColor(c) tvTrendTagCount.setTextColor(c) cvTrendTagHistory.setColor(c) tvFilterPhrase.setTextColor(c) tvMediaDescription.setTextColor(c) tvCardText.setTextColor(c) tvConversationIconsMore.setTextColor(c) tvConversationParticipants.setTextColor(c) (llCardOuter.background as? PreviewCardBorder)?.let { val rgb = c and 0xffffff val alpha = max(1, c ushr (24 + 1)) // 本来の値の半分にする it.color = rgb or (alpha shl 24) } c = column.getAcctColor() this.acct_color = c tvBoostedTime.setTextColor(c) tvTime.setTextColor(c) tvTrendTagDesc.setTextColor(c) tvFilterDetail.setTextColor(c) tvFilterPhrase.setTextColor(c) // 以下のビューの文字色はsetAcct() で設定される // tvBoostedAcct.setTextColor(c) // tvFollowerAcct.setTextColor(c) // tvAcct.setTextColor(c) this.item = item when(item) { is TootStatus -> { val reblog = item.reblog when { reblog == null -> showStatusOrReply(item) item.hasAnyContent() -> { // 引用Renote showReply( R.drawable.ic_repeat, R.string.renote_to, reblog ) showStatus(item) } else -> { // 引用なしブースト showBoost( item.accountRef, item.time_created_at, R.drawable.ic_repeat, R.string.display_name_boosted_by ) showStatusOrReply(item.reblog) } } } is TootAccountRef -> showAccount(item) is TootNotification -> showNotification(item) is TootGap -> showGap() is TootDomainBlock -> showDomainBlock(item) is TootList -> showList(item) is TootMessageHolder -> showMessageHolder(item) // TootTrendTag の後に TootTagを判定すること is TootTrendTag -> showTrendTag(item) is TootTag -> showSearchTag(item) is TootFilter -> showFilter(item) is TootConversationSummary -> { showStatusOrReply(item.last_status) showConversationIcons(item) } is TootScheduled -> { showScheduled(item) } else -> { } } b.report() } private fun showScheduled(item : TootScheduled) { try { llStatus.visibility = View.VISIBLE this.viewRoot.setBackgroundColor(0) showStatusTimeScheduled(activity, tvTime, item) val who = access_info.loginAccount !! val whoRef = TootAccountRef(TootParser(activity, access_info), who) this.status_account = whoRef setAcct(tvAcct, access_info.getFullAcct(who), who.acct) tvName.text = whoRef.decoded_display_name name_invalidator.register(whoRef.decoded_display_name) ivThumbnail.setImageUrl( activity.pref, Styler.calcIconRound(ivThumbnail.layoutParams), access_info.supplyBaseUrl(who.avatar_static), access_info.supplyBaseUrl(who.avatar) ) val content = SpannableString(item.text ?: "") tvMentions.visibility = View.GONE tvContent.text = content content_invalidator.register(content) tvContent.minLines = - 1 val decoded_spoiler_text = SpannableString(item.spoiler_text ?: "") when { decoded_spoiler_text.isNotEmpty() -> { // 元データに含まれるContent Warning を使う llContentWarning.visibility = View.VISIBLE tvContentWarning.text = decoded_spoiler_text spoiler_invalidator.register(decoded_spoiler_text) val cw_shown = ContentWarning.isShown(item.uri, false) showContent(cw_shown) } else -> { // CWしない llContentWarning.visibility = View.GONE llContents.visibility = View.VISIBLE } } val media_attachments = item.media_attachments if(media_attachments?.isEmpty() != false) { flMedia.visibility = View.GONE llMedia.visibility = View.GONE btnShowMedia.visibility = View.GONE } else { flMedia.visibility = View.VISIBLE // hide sensitive media val default_shown = when { column.hide_media_default -> false access_info.dont_hide_nsfw -> true else -> ! item.sensitive } val is_shown = MediaShown.isShown(item.uri, default_shown) btnShowMedia.visibility = if(! is_shown) View.VISIBLE else View.GONE llMedia.visibility = if(! is_shown) View.GONE else View.VISIBLE val sb = StringBuilder() setMedia(media_attachments, sb, ivMedia1, 0) setMedia(media_attachments, sb, ivMedia2, 1) setMedia(media_attachments, sb, ivMedia3, 2) setMedia(media_attachments, sb, ivMedia4, 3) if(sb.isNotEmpty()) { tvMediaDescription.visibility = View.VISIBLE tvMediaDescription.text = sb } setIconDrawableId( activity, btnHideMedia, R.drawable.ic_close, color = content_color, alphaMultiplier = Styler.boost_alpha ) } buttons_for_status?.hide() tvApplication.visibility = View.GONE } catch(ex : Throwable) { } llSearchTag.visibility = View.VISIBLE btnSearchTag.text = activity.getString(R.string.scheduled_status) + " " + TootStatus.formatTime( activity, item.timeScheduledAt, true ) } private fun removeExtraView() { llExtra.scan { v -> if(v is MyNetworkImageView) { v.cancelLoading() } } llExtra.removeAllViews() for(invalidator in extra_invalidator_list) { invalidator.register(null) } extra_invalidator_list.clear() } private fun showConversationIcons(cs : TootConversationSummary) { val last_account_id = cs.last_status.account.id val accountsOther = cs.accounts.filter { it.get().id != last_account_id } if(accountsOther.isNotEmpty()) { llConversationIcons.visibility = View.VISIBLE val size = accountsOther.size tvConversationParticipants.text = if(size <= 1) { activity.getString(R.string.conversation_to) } else { activity.getString(R.string.participants) } fun showIcon(iv : MyNetworkImageView, idx : Int) { val bShown = idx < size iv.visibility = if(bShown) View.VISIBLE else View.GONE if(! bShown) return val who = accountsOther[idx].get() iv.setImageUrl( activity.pref, Styler.calcIconRound(iv.layoutParams), access_info.supplyBaseUrl(who.avatar_static), access_info.supplyBaseUrl(who.avatar) ) } showIcon(ivConversationIcon1, 0) showIcon(ivConversationIcon2, 1) showIcon(ivConversationIcon3, 2) showIcon(ivConversationIcon4, 3) tvConversationIconsMore.text = when { size <= 4 -> "" else -> activity.getString(R.string.participants_and_more) } } if(cs.last_status.in_reply_to_id != null) { llSearchTag.visibility = View.VISIBLE btnSearchTag.text = activity.getString(R.string.show_conversation) } } private fun openConversationSummary() { val cs = item as? TootConversationSummary ?: return if(cs.unread) { cs.unread = false // 表示の更新 list_adapter.notifyChange( reason = "ConversationSummary reset unread", reset = true ) // 未読フラグのクリアをサーバに送る Action_Toot.clearConversationUnread(activity, access_info, cs) } Action_Toot.conversation( activity, activity.nextPosition(column), access_info, cs.last_status ) } private fun showStatusOrReply(item : TootStatus) { val reply = item.reply val in_reply_to_id = item.in_reply_to_id val in_reply_to_account_id = item.in_reply_to_account_id when { reply != null -> showReply( R.drawable.ic_reply, R.string.reply_to, reply ) in_reply_to_id != null && in_reply_to_account_id != null -> { showReply( R.drawable.ic_reply, in_reply_to_account_id, item ) } } showStatus(item) } private fun showTrendTag(item : TootTrendTag) { llTrendTag.visibility = View.VISIBLE tvTrendTagName.text = "#${item.name}" tvTrendTagDesc.text = activity.getString(R.string.people_talking, item.accountDaily, item.accountWeekly) tvTrendTagCount.text = "${item.countDaily}(${item.countWeekly})" cvTrendTagHistory.setHistory(item.history) } private fun showMessageHolder(item : TootMessageHolder) { tvMessageHolder.visibility = View.VISIBLE tvMessageHolder.text = item.text tvMessageHolder.gravity = item.gravity } private fun showNotification(n : TootNotification) { val n_status = n.status val n_accountRef = n.accountRef val n_account = n_accountRef?.get() fun showNotificationStatus(item : TootStatus) { val reblog = item.reblog when { reblog == null -> showStatusOrReply(item) ! item.hasAnyContent() -> { // 通常のブースト。引用なしブースト。 // ブースト表示は通知イベントと被るのでしない showStatusOrReply(reblog) } else -> { // 引用Renote showReply( R.drawable.ic_repeat, R.string.renote_to, reblog ) showStatus(item) } } } when(n.type) { TootNotification.TYPE_FAVOURITE -> { if(n_account != null) showBoost( n_accountRef, n.time_created_at, if(access_info.isNicoru(n_account)) R.drawable.ic_nicoru else R.drawable.ic_star, R.string.display_name_favourited_by ) if(n_status != null) { showNotificationStatus(n_status) } } TootNotification.TYPE_REBLOG -> { if(n_account != null) showBoost( n_accountRef, n.time_created_at, R.drawable.ic_repeat, R.string.display_name_boosted_by ) if(n_status != null) { showNotificationStatus(n_status) } } TootNotification.TYPE_RENOTE -> { // 引用のないreblog if(n_account != null) showBoost( n_accountRef, n.time_created_at, R.drawable.ic_repeat, R.string.display_name_boosted_by ) if(n_status != null) { showNotificationStatus(n_status) } } TootNotification.TYPE_FOLLOW -> { if(n_account != null) { showBoost( n_accountRef, n.time_created_at, R.drawable.ic_follow_plus, R.string.display_name_followed_by ) showAccount(n_accountRef) } } TootNotification.TYPE_UNFOLLOW -> { if(n_account != null) { showBoost( n_accountRef, n.time_created_at, R.drawable.ic_follow_cross, R.string.display_name_unfollowed_by ) showAccount(n_accountRef) } } TootNotification.TYPE_MENTION, TootNotification.TYPE_REPLY -> { if(! bSimpleList && ! access_info.isMisskey) { if(n_account != null) { if(n_status?.in_reply_to_id != null || n_status?.reply != null ) { // トゥート内部に「~への返信」を表示するので、 // 通知イベントの「~からの返信」は表示しない } else { // 返信ではなくメンションの場合は「~からの返信」を表示する showBoost( n_accountRef, n.time_created_at, R.drawable.ic_reply, R.string.display_name_mentioned_by ) } } } if(n_status != null) { showNotificationStatus(n_status) } } TootNotification.TYPE_REACTION -> { val reaction = MisskeyReaction.shortcodeMap[n.reaction ?: ""] if(n_account != null) showBoost( n_accountRef, n.time_created_at, R.drawable.ic_question, // not used R.string.display_name_reaction_by , reactionDrawableId = reaction?.btnDrawableId ) if(n_status != null) { showNotificationStatus(n_status) } } TootNotification.TYPE_QUOTE -> { if(n_account != null) showBoost( n_accountRef, n.time_created_at, R.drawable.ic_repeat, R.string.display_name_quoted_by ) if(n_status != null) { showNotificationStatus(n_status) } } TootNotification.TYPE_VOTE -> { if(n_account != null) showBoost( n_accountRef, n.time_created_at, R.drawable.ic_vote, R.string.display_name_voted_by ) if(n_status != null) { showNotificationStatus(n_status) } } TootNotification.TYPE_FOLLOW_REQUEST -> { if(n_account != null) showBoost( n_accountRef, n.time_created_at, R.drawable.ic_follow_wait, R.string.display_name_follow_request_by ) boostedAction = { activity.addColumn( activity.nextPosition(column) , access_info , Column.TYPE_FOLLOW_REQUESTS ) } } else -> { if(n_account != null) showBoost( n_accountRef, n.time_created_at, R.drawable.ic_question, R.string.unknown_notification_from ) if(n_status != null) { showNotificationStatus(n_status) } tvMessageHolder.visibility = View.VISIBLE tvMessageHolder.text = "notification type is ${n.type}" tvMessageHolder.gravity = Gravity.CENTER } } } private fun showList(list : TootList) { llList.visibility = View.VISIBLE btnListTL.text = list.title } private fun showDomainBlock(domain_block : TootDomainBlock) { llSearchTag.visibility = View.VISIBLE btnSearchTag.text = domain_block.domain } private fun showFilter(filter : TootFilter) { llFilter.visibility = View.VISIBLE tvFilterPhrase.text = filter.phrase val sb = StringBuffer() // sb.append(activity.getString(R.string.filter_context)) .append(": ") .append(filter.getContextNames(activity).joinToString("/")) // val flags = ArrayList() if(filter.irreversible) flags.add(activity.getString(R.string.filter_irreversible)) if(filter.whole_word) flags.add(activity.getString(R.string.filter_word_match)) if(flags.isNotEmpty()) { sb.append('\n') .append(flags.joinToString(", ")) } // if(filter.time_expires_at != 0L) { sb.append('\n') .append(activity.getString(R.string.filter_expires_at)) .append(": ") .append(TootStatus.formatTime(activity, filter.time_expires_at, false)) } tvFilterDetail.text = sb.toString() } private fun showSearchTag(tag : TootTag) { llSearchTag.visibility = View.VISIBLE btnSearchTag.text = "#" + tag.name } private fun showGap() { llSearchTag.visibility = View.VISIBLE btnSearchTag.text = activity.getString(R.string.read_gap) } private fun showReply( iconId : Int, text : Spannable ) { llReply.visibility = View.VISIBLE setIconDrawableId( activity, ivReply, iconId, color = content_color, alphaMultiplier = Styler.boost_alpha ) tvReply.text = text reply_invalidator.register(text) } private fun showReply( iconId : Int, stringId : Int, reply : TootStatus ) { status_reply = reply // val who = reply.account // showStatusTime(activity, tvReplyTime, who, time = reply.time_created_at) // setAcct(tvReplyAcct, access_info.getFullAcct(who), who.acct) val text = reply.accountRef.decoded_display_name.intoStringResource(activity, stringId) showReply(iconId, text) } private fun showReply( iconId : Int, accountId : EntityId, replyStatus : TootStatus ) { llReply.visibility = View.VISIBLE val name = if(accountId == replyStatus.account.id) { AcctColor.getNicknameWithColor(access_info.getFullAcct(replyStatus.account)) } else { val m = replyStatus.mentions?.find { it.id == accountId } if(m != null) { AcctColor.getNicknameWithColor(access_info.getFullAcct(m.acct)) } else { SpannableString("ID(${accountId})") } } val text = name.intoStringResource(activity, R.string.reply_to) // val who = reply.account // showStatusTime(activity, tvReplyTime, who, time = reply.time_created_at) // setAcct(tvReplyAcct, access_info.getFullAcct(who), who.acct) showReply(iconId, text) } private fun showBoost( whoRef : TootAccountRef, time : Long, iconId : Int, string_id : Int, reactionDrawableId : Int? = null ) { boost_account = whoRef val who = whoRef.get() val text : Spannable = if(string_id == R.string.display_name_followed_by) { // フォローの場合 decoded_display_name が2箇所で表示に使われるのを避ける必要がある who.decodeDisplayName(activity) } else { // それ以外の場合は decoded_display_name を再利用して構わない whoRef.decoded_display_name }.intoStringResource(activity, string_id) if(reactionDrawableId != null) { setIconDrawableId(activity, ivBoosted, reactionDrawableId) } else { setIconDrawableId( activity, ivBoosted, iconId, color = content_color, alphaMultiplier = Styler.boost_alpha ) } boost_time = time llBoosted.visibility = View.VISIBLE showStatusTime(activity, tvBoostedTime, who, time = time) tvBoosted.text = text boost_invalidator.register(text) setAcct(tvBoostedAcct, access_info.getFullAcct(who), who.acct) } private fun showAccount(whoRef : TootAccountRef) { follow_account = whoRef val who = whoRef.get() llFollow.visibility = View.VISIBLE ivFollow.setImageUrl( activity.pref, Styler.calcIconRound(ivFollow.layoutParams), access_info.supplyBaseUrl(who.avatar_static), access_info.supplyBaseUrl(who.avatar) ) tvFollowerName.text = whoRef.decoded_display_name follow_invalidator.register(whoRef.decoded_display_name) setAcct(tvFollowerAcct, access_info.getFullAcct(who), who.acct) val relation = UserRelation.load(access_info.db_id, who.id) Styler.setFollowIcon( activity, btnFollow, ivFollowedBy, relation, who, content_color, alphaMultiplier = Styler.boost_alpha ) if(column.column_type == Column.TYPE_FOLLOW_REQUESTS) { llFollowRequest.visibility = View.VISIBLE } } private fun showStatus(status : TootStatus) { if(status.filtered) { showMessageHolder(TootMessageHolder(activity.getString(R.string.filtered))) return } this.status_showing = status llStatus.visibility = View.VISIBLE if(status.conversation_main) { this.viewRoot.setBackgroundColor( (getAttributeColor( activity, R.attr.colorImageButtonAccent ) and 0xffffff) or 0x20000000 ) } else { val c = when(status.getBackgroundColorType(access_info)) { TootVisibility.UnlistedHome -> toot_color_unlisted TootVisibility.PrivateFollowers -> toot_color_follower TootVisibility.DirectSpecified -> toot_color_direct_user TootVisibility.DirectPrivate -> toot_color_direct_me else -> 0 } if(c != 0) { this.viewRoot.setBackgroundColor(c) } } showStatusTime(activity, tvTime, who = status.account, status = status) val whoRef = status.accountRef val who = whoRef.get() this.status_account = whoRef setAcct(tvAcct, access_info.getFullAcct(who), who.acct) // if(who == null) { // tvName.text = "?" // name_invalidator.register(null) // ivThumbnail.setImageUrl(activity.pref, 16f, null, null) // } else { tvName.text = whoRef.decoded_display_name name_invalidator.register(whoRef.decoded_display_name) ivThumbnail.setImageUrl( activity.pref, Styler.calcIconRound(ivThumbnail.layoutParams), access_info.supplyBaseUrl(who.avatar_static), access_info.supplyBaseUrl(who.avatar) ) // } showInstanceTicker(who) var content = status.decoded_content // ニコフレのアンケートの表示 val enquete = status.enquete if(enquete != null) { if(access_info.isMisskey || NicoEnquete.TYPE_ENQUETE == enquete.type) { val question = enquete.decoded_question val items = enquete.items if(question.isNotBlank()) content = question if(items != null) { val now = System.currentTimeMillis() var n = 0 for(item in items) { makeEnqueteChoiceView(enquete, now, n ++, item) } } if(! access_info.isMisskey) makeEnqueteTimerView(enquete) } } showPreviewCard(status) // if( status.decoded_tags == null ){ // tvTags.setVisibility( View.GONE ); // }else{ // tvTags.setVisibility( View.VISIBLE ); // tvTags.setText( status.decoded_tags ); // } if(status.decoded_mentions.isEmpty()) { tvMentions.visibility = View.GONE } else { tvMentions.visibility = View.VISIBLE tvMentions.text = status.decoded_mentions } if(status.time_deleted_at > 0L) { val s = SpannableStringBuilder() .append('(') .append( activity.getString( R.string.deleted_at, TootStatus.formatTime(activity, status.time_deleted_at, true) ) ) .append(')') content = s } tvContent.text = content content_invalidator.register(content) activity.checkAutoCW(status, content) val r = status.auto_cw tvContent.minLines = r?.originalLineCount ?: - 1 val decoded_spoiler_text = status.decoded_spoiler_text when { decoded_spoiler_text.isNotEmpty() -> { // 元データに含まれるContent Warning を使う llContentWarning.visibility = View.VISIBLE tvContentWarning.text = status.decoded_spoiler_text spoiler_invalidator.register(status.decoded_spoiler_text) val cw_shown = ContentWarning.isShown(status, false) showContent(cw_shown) } r?.decoded_spoiler_text != null -> { // 自動CW llContentWarning.visibility = View.VISIBLE tvContentWarning.text = r.decoded_spoiler_text spoiler_invalidator.register(r.decoded_spoiler_text) val cw_shown = ContentWarning.isShown(status, false) showContent(cw_shown) } else -> { // CWしない llContentWarning.visibility = View.GONE llContents.visibility = View.VISIBLE } } val media_attachments = status.media_attachments if(media_attachments == null || media_attachments.isEmpty()) { flMedia.visibility = View.GONE llMedia.visibility = View.GONE btnShowMedia.visibility = View.GONE } else { flMedia.visibility = View.VISIBLE // hide sensitive media val default_shown = when { column.hide_media_default -> false access_info.dont_hide_nsfw -> true else -> ! status.sensitive } val is_shown = MediaShown.isShown(status, default_shown) btnShowMedia.visibility = if(! is_shown) View.VISIBLE else View.GONE llMedia.visibility = if(! is_shown) View.GONE else View.VISIBLE val sb = StringBuilder() setMedia(media_attachments, sb, ivMedia1, 0) setMedia(media_attachments, sb, ivMedia2, 1) setMedia(media_attachments, sb, ivMedia3, 2) setMedia(media_attachments, sb, ivMedia4, 3) if(sb.isNotEmpty()) { tvMediaDescription.visibility = View.VISIBLE tvMediaDescription.text = sb } setIconDrawableId( activity, btnHideMedia, R.drawable.ic_close, color = content_color, alphaMultiplier = Styler.boost_alpha ) } makeReactionsView(status) buttons_for_status?.bind(status, (item as? TootNotification)) val application = status.application if(application != null && (column.column_type == Column.TYPE_CONVERSATION || Pref.bpShowAppName(activity.pref)) ) { tvApplication.visibility = View.VISIBLE tvApplication.text = activity.getString(R.string.application_is, application.name ?: "") } else { tvApplication.visibility = View.GONE } } private fun showInstanceTicker(who : TootAccount) { try { if(! Column.useInstanceTicker) return val host = who.host // LTLでホスト名が同じならTickerを表示しない when(column.column_type) { Column.TYPE_LOCAL, Column.TYPE_LOCAL_AROUND -> { if(host == access_info.host) return } } val item = InstanceTicker.lastList[host] ?: return tvInstanceTicker.text = item.name tvInstanceTicker.textColor = item.colorText val density = activity.density val lp = ivInstanceTicker.layoutParams lp.height = (density * 16f + 0.5f).toInt() lp.width = (density * item.imageWidth + 0.5f).toInt() ivInstanceTicker.layoutParams = lp ivInstanceTicker.setImageUrl(activity.pref, 0f, item.image) val colorBg = item.colorBg when { colorBg.isEmpty() -> { tvInstanceTicker.background = null ivInstanceTicker.background = null } colorBg.size == 1 -> { tvInstanceTicker.setBackgroundColor(colorBg.first()) ivInstanceTicker.setBackgroundColor(colorBg.first()) } else -> { ivInstanceTicker.setBackgroundColor(colorBg.last()) tvInstanceTicker.background = colorBg.getGradation() } } llInstanceTicker.visibility = View.VISIBLE llInstanceTicker.requestLayout() } catch(ex : Throwable) { log.trace(ex) } } private fun showStatusTime( activity : ActMain, tv : TextView, @Suppress("UNUSED_PARAMETER") who : TootAccount, status : TootStatus? = null, time : Long? = null ) { val sb = SpannableStringBuilder() // if(access_info.getFullAcct(who) == "unarist@mstdn.maud.io") { // // if(sb.isNotEmpty()) sb.append(' ') // // val start = sb.length // sb.append("unarist") // val end = sb.length // val icon_id = R.drawable.unarist // sb.setSpan( // EmojiImageSpan(activity, icon_id), // start, // end, // Spanned.SPAN_EXCLUSIVE_EXCLUSIVE // ) // } if(status != null) { if(status.account.isAdmin) { if(sb.isNotEmpty()) sb.append('\u200B') sb.appendColorShadeIcon(activity, R.drawable.ic_shield, "admin") } if(status.account.isPro) { if(sb.isNotEmpty()) sb.append('\u200B') sb.appendColorShadeIcon(activity, R.drawable.ic_authorized, "pro") } if(status.account.isCat) { if(sb.isNotEmpty()) sb.append('\u200B') sb.appendColorShadeIcon(activity, R.drawable.ic_cat, "cat") } // botマーク if(status.account.bot) { if(sb.isNotEmpty()) sb.append('\u200B') sb.appendColorShadeIcon(activity, R.drawable.ic_bot, "bot") } // mobileマーク if(status.viaMobile) { if(sb.isNotEmpty()) sb.append('\u200B') sb.appendColorShadeIcon(activity, R.drawable.ic_mobile, "mobile") } // NSFWマーク if(status.hasMedia() && status.sensitive) { if(sb.isNotEmpty()) sb.append('\u200B') sb.appendColorShadeIcon(activity, R.drawable.ic_eye_off, "NSFW") } // visibility val visIconId = Styler.getVisibilityIconId(access_info.isMisskey, status.visibility) if(R.drawable.ic_public != visIconId) { if(sb.isNotEmpty()) sb.append('\u200B') sb.appendColorShadeIcon( activity, visIconId, Styler.getVisibilityString( activity, access_info.isMisskey, status.visibility ) ) } // pinned if(status.pinned) { if(sb.isNotEmpty()) sb.append('\u200B') sb.appendColorShadeIcon(activity, R.drawable.ic_pin, "pinned") // val start = sb.length // sb.append("pinned") // val end = sb.length // val icon_id = Styler.getAttributeResourceId(activity, R.attr.ic_pin) // sb.setSpan( // EmojiImageSpan(activity, icon_id), // start, // end, // Spanned.SPAN_EXCLUSIVE_EXCLUSIVE // ) } // unread if(status.conversationSummary?.unread == true) { if(sb.isNotEmpty()) sb.append('\u200B') val color = if(MyClickableSpan.defaultLinkColor != 0) { MyClickableSpan.defaultLinkColor } else { getAttributeColor(activity, R.attr.colorLink) } sb.appendColorShadeIcon( activity, R.drawable.ic_unread, "unread", color = color ) } } if(sb.isNotEmpty()) sb.append(' ') sb.append( when { time != null -> TootStatus.formatTime( activity, time, column.column_type != Column.TYPE_CONVERSATION ) status != null -> TootStatus.formatTime( activity, status.time_created_at, column.column_type != Column.TYPE_CONVERSATION ) else -> "?" } ) tv.text = sb } private fun showStatusTimeScheduled( activity : ActMain, tv : TextView, item : TootScheduled ) { val sb = SpannableStringBuilder() // NSFWマーク if(item.hasMedia() && item.sensitive) { if(sb.isNotEmpty()) sb.append('\u200B') sb.appendColorShadeIcon(activity, R.drawable.ic_eye_off, "NSFW") } // visibility val visIconId = Styler.getVisibilityIconId(access_info.isMisskey, item.visibility) if(R.drawable.ic_public != visIconId) { if(sb.isNotEmpty()) sb.append('\u200B') sb.appendColorShadeIcon( activity, visIconId, Styler.getVisibilityString( activity, access_info.isMisskey, item.visibility ) ) } if(sb.isNotEmpty()) sb.append(' ') sb.append( TootStatus.formatTime( activity, item.timeScheduledAt, column.column_type != Column.TYPE_CONVERSATION ) ) tv.text = sb } // fun updateRelativeTime() { // val boost_time = this.boost_time // if(boost_time != 0L) { // tvBoostedTime.text = TootStatus.formatTime(tvBoostedTime.context, boost_time, true) // } // val status_showing = this.status_showing // if(status_showing != null) { // showStatusTime(activity, status_showing) // } // } private fun setAcct(tv : TextView, acctLong : String, acctShort : String?) { val ac = AcctColor.load(acctLong) tv.text = when { AcctColor.hasNickname(ac) -> ac.nickname Pref.bpShortAcctLocalUser(App1.pref) -> "@" + (acctShort ?: "?") else -> acctLong } tv.setTextColor(if(AcctColor.hasColorForeground(ac)) ac.color_fg else this.acct_color) if(AcctColor.hasColorBackground(ac)) { tv.setBackgroundColor(ac.color_bg) } else { ViewCompat.setBackground(tv, null) } tv.setPaddingRelative(activity.acct_pad_lr, 0, activity.acct_pad_lr, 0) } private fun showContent(shown : Boolean) { llContents.visibility = if(shown) View.VISIBLE else View.GONE btnContentWarning.setText(if(shown) R.string.hide else R.string.show) status_showing?.let { status -> val r = status.auto_cw tvContent.minLines = r?.originalLineCount ?: - 1 if(r?.decoded_spoiler_text != null) { // 自動CWの場合はContentWarningのテキストを切り替える tvContentWarning.text = if(shown) activity.getString(R.string.auto_cw_prefix) else r.decoded_spoiler_text } } } private fun setMedia( media_attachments : ArrayList, sbDesc : StringBuilder, iv : MyNetworkImageView, idx : Int ) { val ta = if(idx < media_attachments.size) media_attachments[idx] else null if(ta == null) { iv.visibility = View.GONE return } iv.visibility = View.VISIBLE iv.setFocusPoint(ta.focusX, ta.focusY) if(Pref.bpDontCropMediaThumb(App1.pref)) { iv.scaleType = ImageView.ScaleType.FIT_CENTER } else { iv.setScaleTypeForMedia() } when(ta.type) { TootAttachmentLike.TYPE_AUDIO -> { iv.setMediaType(0) iv.setDefaultImage(defaultColorIcon(activity,R.drawable.wide_music)) iv.setImageUrl(activity.pref, 0f, null) } TootAttachmentLike.TYPE_UNKNOWN -> { iv.setMediaType(0) iv.setDefaultImage(defaultColorIcon(activity,R.drawable.wide_question)) iv.setImageUrl(activity.pref, 0f, null) } else -> { val url = ta.urlForThumbnail when { url?.isEmpty() != false -> { iv.setMediaType(0) iv.setDefaultImage(defaultColorIcon(activity,R.drawable.wide_question)) iv.setImageUrl(activity.pref, 0f, null) } else -> { iv.setMediaType( when(ta.type) { TootAttachmentLike.TYPE_VIDEO -> R.drawable.media_type_video TootAttachmentLike.TYPE_GIFV -> R.drawable.media_type_gifv else -> 0 } ) iv.setDefaultImage(null) iv.setImageUrl( activity.pref, 0f, access_info.supplyBaseUrl(url), access_info.supplyBaseUrl(url) ) } } } } val description = ta.description if(description != null && description.isNotEmpty()) { val lp = LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT ) lp.topMargin = (0.5f + activity.density * 3f).toInt() val tv = MyTextView(activity) tv.layoutParams = lp // tv.movementMethod = MyLinkMovementMethod if(! activity.timeline_font_size_sp.isNaN()) { tv.textSize = activity.timeline_font_size_sp } tv.setTextColor(content_color) if(ta.description?.isNotEmpty() == true) { if(sbDesc.isNotEmpty()) sbDesc.append("\n") val desc = activity.getString(R.string.media_description, idx + 1, ta.description) sbDesc.append(desc) } } } private val defaultBoostedAction : () -> Unit = { val pos = activity.nextPosition(column) val notification = (item as? TootNotification) boost_account?.let { whoRef -> if(access_info.isPseudo) { DlgContextMenu(activity, column, whoRef, null, notification, tvContent).show() } else { Action_User.profileLocal(activity, pos, access_info, whoRef.get()) } } } private var boostedAction : () -> Unit = defaultBoostedAction override fun onClick(v : View) { val pos = activity.nextPosition(column) val item = this.item val notification = (item as? TootNotification) when(v) { btnHideMedia -> { status_showing?.let { status -> MediaShown.save(status, false) btnShowMedia.visibility = View.VISIBLE llMedia.visibility = View.GONE } if( item is TootScheduled){ MediaShown.save(item.uri, false) btnShowMedia.visibility = View.VISIBLE llMedia.visibility = View.GONE } } btnShowMedia -> { status_showing?.let { status -> MediaShown.save(status, true) btnShowMedia.visibility = View.GONE llMedia.visibility = View.VISIBLE } if( item is TootScheduled){ MediaShown.save(item.uri, true) btnShowMedia.visibility = View.GONE llMedia.visibility = View.VISIBLE } } ivMedia1 -> clickMedia(0) ivMedia2 -> clickMedia(1) ivMedia3 -> clickMedia(2) ivMedia4 -> clickMedia(3) btnContentWarning -> { status_showing?.let { status -> val new_shown = llContents.visibility == View.GONE ContentWarning.save(status, new_shown) // 1個だけ開閉するのではなく、例えば通知TLにある複数の要素をまとめて開閉するなどある list_adapter.notifyChange(reason = "ContentWarning onClick", reset = true) } if(item is TootScheduled){ val new_shown = llContents.visibility == View.GONE ContentWarning.save(item.uri, new_shown) // 1個だけ開閉するのではなく、例えば通知TLにある複数の要素をまとめて開閉するなどある list_adapter.notifyChange(reason = "ContentWarning onClick", reset = true) } } ivThumbnail -> status_account?.let { whoRef -> when { access_info.isNA -> DlgContextMenu( activity, column, whoRef, null, notification, tvContent ).show() // 2018/12/26 疑似アカウントでもプロフカラムを表示する https://github.com/tootsuite/mastodon/commit/108b2139cd87321f6c0aec63ef93db85ce30bfec else -> Action_User.profileLocal( activity, pos, access_info, whoRef.get() ) } } llBoosted -> boostedAction() llReply -> { val s = status_reply if(s != null) { Action_Toot.conversation(activity, pos, access_info, s) } else { val id = status_showing?.in_reply_to_id if(id != null) { Action_Toot.conversationLocal(activity, pos, access_info, id) } } } llFollow -> follow_account?.let { whoRef -> if(access_info.isPseudo) { DlgContextMenu(activity, column, whoRef, null, notification, tvContent).show() } else { Action_User.profileLocal(activity, pos, access_info, whoRef.get()) } } btnFollow -> follow_account?.let { who -> DlgContextMenu(activity, column, who, null, notification, tvContent).show() } btnSearchTag, llTrendTag -> when(item) { is TootConversationSummary -> openConversationSummary() is TootGap -> column.startGap(item) is TootDomainBlock -> { val domain = item.domain AlertDialog.Builder(activity) .setMessage(activity.getString(R.string.confirm_unblock_domain, domain)) .setNegativeButton(R.string.cancel, null) .setPositiveButton(R.string.ok) { _, _ -> Action_Instance.blockDomain( activity, access_info, domain, false ) } .show() } is TootTag -> { Action_HashTag.timeline( activity, activity.nextPosition(column), access_info, item.name // #を含まない ) } is TootScheduled -> { ActionsDialog() .addAction(activity.getString(R.string.delete)) { Action_Toot.deleteScheduledPost(activity, access_info, item) { column.onScheduleDeleted(item) showToast(activity, false, R.string.scheduled_post_deleted) } } .addAction(activity.getString(R.string.edit)) { Action_Toot.editScheduledPost(activity, access_info, item) } .show(activity) } } btnListTL -> if(item is TootList) { activity.addColumn(pos, access_info, Column.TYPE_LIST_TL, item.id) } btnListMore -> if(item is TootList) { ActionsDialog() .addAction(activity.getString(R.string.list_timeline)) { activity.addColumn(pos, access_info, Column.TYPE_LIST_TL, item.id) } .addAction(activity.getString(R.string.list_member)) { activity.addColumn( false, pos, access_info, Column.TYPE_LIST_MEMBER, item.id ) } .addAction(activity.getString(R.string.rename)) { Action_List.rename(activity, access_info, item) } .addAction(activity.getString(R.string.delete)) { Action_List.delete(activity, access_info, item) } .show(activity, item.title) } btnFollowRequestAccept -> follow_account?.let { whoRef -> val who = whoRef.get() DlgConfirm.openSimple( activity, activity.getString( R.string.follow_accept_confirm, AcctColor.getNickname(access_info.getFullAcct(who)) ) ) { Action_Follow.authorizeFollowRequest(activity, access_info, whoRef, true) } } btnFollowRequestDeny -> follow_account?.let { whoRef -> val who = whoRef.get() DlgConfirm.openSimple( activity, activity.getString( R.string.follow_deny_confirm, AcctColor.getNickname(access_info.getFullAcct(who)) ) ) { Action_Follow.authorizeFollowRequest(activity, access_info, whoRef, false) } } llFilter -> if(item is TootFilter) { openFilterMenu(item) } ivCardImage -> status_showing?.card?.let { card -> val originalStatus = card.originalStatus if(originalStatus != null) { Action_Toot.conversation( activity, activity.nextPosition(column), access_info, originalStatus ) } else { val url = card.url if(url?.isNotEmpty() == true) { ChromeTabOpener( activity, pos, url, accessInfo = access_info ).open() } } } llConversationIcons -> openConversationSummary() } } override fun onLongClick(v : View) : Boolean { val notification = (item as? TootNotification) when(v) { ivThumbnail -> { status_account?.let { who -> DlgContextMenu( activity, column, who, null, notification, tvContent ).show() } return true } llBoosted -> { boost_account?.let { who -> DlgContextMenu( activity, column, who, null, notification, tvContent ).show() } return true } llReply -> { val s = status_reply if(s != null) { DlgContextMenu( activity, column, s.accountRef, s, notification, tvContent ).show() } else { val id = status_showing?.in_reply_to_id if(id != null) { Action_Toot.conversationLocal( activity, activity.nextPosition(column), access_info, id ) } } } llFollow -> { follow_account?.let { whoRef -> DlgContextMenu( activity, column, whoRef, null, notification ).show() } return true } btnFollow -> { follow_account?.let { whoRef -> Action_Follow.followFromAnotherAccount( activity, activity.nextPosition(column), access_info, whoRef.get() ) } return true } ivCardImage -> Action_Toot.conversationOtherInstance( activity, activity.nextPosition(column), status_showing?.card?.originalStatus ) btnSearchTag, llTrendTag -> { val item = this.item when(item) { // is TootGap -> column.startGap(item) // // is TootDomainBlock -> { // val domain = item.domain // AlertDialog.Builder(activity) // .setMessage(activity.getString(R.string.confirm_unblock_domain, domain)) // .setNegativeButton(R.string.cancel, null) // .setPositiveButton(R.string.ok) { _, _ -> Action_Instance.blockDomain(activity, access_info, domain, false) } // .show() // } is TootTag -> { // search_tag は#を含まない val tagEncoded = item.name.encodePercent() val host = access_info.host val url = "https://$host/tags/$tagEncoded" Action_HashTag.timelineOtherInstance( activity = activity, pos = activity.nextPosition(column), url = url, host = host, tag_without_sharp = item.name ) } } return true } } return false } private fun clickMedia(i : Int) { try { val media_attachments = status_showing?.media_attachments ?: (item as? TootScheduled)?.media_attachments ?: return val item = if(i < media_attachments.size) media_attachments[i] else return when(item) { is TootAttachmentMSP -> { // マストドン検索ポータルのデータではmedia_attachmentsが簡略化されている // 会話の流れを表示する Action_Toot.conversationOtherInstance( activity, activity.nextPosition(column), status_showing ) } is TootAttachment -> { if(Pref.bpUseInternalMediaViewer(App1.pref)) { // 内蔵メディアビューア val serviceType = when(access_info.isMisskey) { true -> ServiceType.MISSKEY else -> ServiceType.MASTODON } ActMediaViewer.open(activity, serviceType, media_attachments, i) } else { // ブラウザで開く App1.openCustomTab(activity, item) } } } } catch(ex : Throwable) { log.trace(ex) } } private fun showPreviewCard(status : TootStatus) { if(Pref.bpDontShowPreviewCard(activity.pref)) return val card = status.card ?: return // 会話カラムで返信ステータスなら捏造したカードを表示しない if(column.column_type == Column.TYPE_CONVERSATION && card.originalStatus != null && status.reply != null ) { return } var bShown = false val sb = StringBuilder() addLinkAndCaption( sb, activity.getString(R.string.card_header_card), card.url, card.title ) addLinkAndCaption( sb, activity.getString(R.string.card_header_author), card.author_url, card.author_name ) addLinkAndCaption( sb, activity.getString(R.string.card_header_provider), card.provider_url, card.provider_name ) val description = card.description if(description != null && description.isNotEmpty()) { if(sb.isNotEmpty()) sb.append("
") val limit = Pref.spCardDescriptionLength.toInt(activity.pref) sb.append( HTMLDecoder.encodeEntity( ellipsize( description, if(limit <= 0) 64 else limit ) ) ) } if(sb.isNotEmpty()) { val text = DecodeOptions(activity, access_info, forceHtml = true).decodeHTML(sb.toString()) if(text.isNotEmpty()) { tvCardText.visibility = View.VISIBLE tvCardText.text = text bShown = true } } val image = card.image if(image != null && image.isNotEmpty()) { ivCardImage.visibility = View.VISIBLE ivCardImage.setImageUrl( activity.pref, 0f, access_info.supplyBaseUrl(image), access_info.supplyBaseUrl(image) ) bShown = true } if(bShown) { llCardOuter.visibility = View.VISIBLE } } private fun addLinkAndCaption( sb : StringBuilder, header : String, url : String?, caption : String? ) { if(url.isNullOrEmpty() && caption.isNullOrEmpty()) return if(sb.isNotEmpty()) sb.append("
") sb.append(HTMLDecoder.encodeEntity(header)).append(": ") if(url != null && url.isNotEmpty()) { sb.append("") } sb.append( HTMLDecoder.encodeEntity( when { caption != null && caption.isNotEmpty() -> caption url != null && url.isNotEmpty() -> url else -> "???" } ) ) if(url != null && url.isNotEmpty()) { sb.append("") } } private fun makeReactionsView(status : TootStatus) { if(! access_info.isMisskey) return val reactionsCount = status.reactionCounts val density = activity.density val buttonHeight = ActMain.boostButtonSize val marginBetween = (ActMain.boostButtonSize.toFloat() * 0.05f + 0.5f).toInt() val paddingH = (buttonHeight.toFloat() * 0.1f + 0.5f).toInt() val paddingV = (buttonHeight.toFloat() * 0.1f + 0.5f).toInt() val compoundPaddingDp = 0f // ActMain.boostButtonSize.toFloat() * 0f / activity.density val box = FlexboxLayout(activity) val boxLp = LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT ) box.layoutParams = boxLp boxLp.topMargin = (0.5f + density * 3f).toInt() box.flexWrap = FlexWrap.WRAP box.justifyContent = JustifyContent.FLEX_START // +ボタン run { val b = ImageButton(activity) val blp = FlexboxLayout.LayoutParams( buttonHeight, buttonHeight ) blp.endMargin = marginBetween b.layoutParams = blp b.background = ContextCompat.getDrawable( activity, R.drawable.btn_bg_transparent ) val hasMyReaction = status.myReaction?.isNotEmpty() == true b.contentDescription = activity.getString(if(hasMyReaction) R.string.reaction_remove else R.string.reaction_add) b.scaleType = ImageView.ScaleType.FIT_CENTER b.padding = paddingV b.setOnClickListener { if(hasMyReaction) { removeReaction(status, false) } else { addReaction(status, null) } } b.setOnLongClickListener { Action_Toot.reactionFromAnotherAccount( activity, access_info, status_showing ) true } setIconDrawableId( activity, b, if(hasMyReaction) R.drawable.ic_remove else R.drawable.ic_add, color = content_color, alphaMultiplier = Styler.boost_alpha ) box.addView(b) } var lastButton : View? = null for(mr in MisskeyReaction.values()) { val count = reactionsCount?.get(mr.shortcode) if(count == null || count <= 0) continue val b = CountImageButton(activity) val blp = FlexboxLayout.LayoutParams( FlexboxLayout.LayoutParams.WRAP_CONTENT, buttonHeight ) b.minimumWidth = buttonHeight b.imageResource = mr.btnDrawableId b.scaleType = ImageView.ScaleType.FIT_CENTER b.layoutParams = blp blp.endMargin = marginBetween b.background = ContextCompat.getDrawable( activity, R.drawable.btn_bg_transparent ) b.setTextColor(content_color) b.setPaddingAndText( paddingH, paddingV , count.toString() , 14f , compoundPaddingDp ) b.tag = mr.shortcode b.setOnClickListener { addReaction(status, it.tag as? String) } b.setOnLongClickListener { Action_Toot.reactionFromAnotherAccount( activity, access_info, status_showing, it.tag as? String ) true } box.addView(b) lastButton = b } if(lastButton != null) { val lp = lastButton.layoutParams if(lp is ViewGroup.MarginLayoutParams) { lp.endMargin = 0 } } llExtra.addView(box) } private fun addReaction(status : TootStatus, code : String?) { if(status.myReaction?.isNotEmpty() == true) { showToast(activity, false, R.string.already_reactioned) return } if(access_info.isPseudo || ! access_info.isMisskey) return if(code == null) { val ad = ActionsDialog() for(mr in MisskeyReaction.values()) { val newCode = mr.shortcode val sb = SpannableStringBuilder() .appendDrawableIcon(activity, mr.drawableId, " ") .append(' ') .append(mr.shortcode) ad.addAction(sb) { addReaction(status, newCode) } } ad.show(activity) return } TootTaskRunner(activity, progress_style = TootTaskRunner.PROGRESS_NONE).run(access_info, object : TootTask { override fun background(client : TootApiClient) : TootApiResult? { val params = access_info.putMisskeyApiToken(JSONObject()) .put("noteId", status.id.toString()) .put("reaction", code) @Suppress("UnnecessaryVariable") val result = client.request("/api/notes/reactions/create", params.toPostRequestBuilder()) // 成功すると204 no content return result } override fun handleResult(result : TootApiResult?) { result ?: return val error = result.error if(error != null) { showToast(activity, false, error) return } if((result.response?.code() ?: - 1) in 200 until 300) { if(status.increaseReaction(code, true, "addReaction")) { // 1個だけ描画更新するのではなく、TLにある複数の要素をまとめて更新する list_adapter.notifyChange(reason = "addReaction complete", reset = true) } } } }) } private fun removeReaction(status : TootStatus, confirmed : Boolean = false) { val reaction = status.myReaction if(reaction?.isNotEmpty() != true) { showToast(activity, false, R.string.not_reactioned) return } if(access_info.isPseudo || ! access_info.isMisskey) return if(! confirmed) { AlertDialog.Builder(activity) .setMessage(activity.getString(R.string.reaction_remove_confirm, reaction)) .setNegativeButton(R.string.cancel, null) .setPositiveButton(R.string.ok) { _, _ -> removeReaction(status, confirmed = true) } .show() return } TootTaskRunner(activity, progress_style = TootTaskRunner.PROGRESS_NONE).run(access_info, object : TootTask { override fun background(client : TootApiClient) : TootApiResult? = // 成功すると204 no content client.request( "/api/notes/reactions/delete", access_info.putMisskeyApiToken(JSONObject()) .put("noteId", status.id.toString()) .toPostRequestBuilder() ) override fun handleResult(result : TootApiResult?) { result ?: return val error = result.error if(error != null) { showToast(activity, false, error) return } if((result.response?.code() ?: - 1) in 200 until 300) { if(status.decreaseReaction(reaction, true, "removeReaction")) { // 1個だけ描画更新するのではなく、TLにある複数の要素をまとめて更新する list_adapter.notifyChange( reason = "removeReaction complete", reset = true ) } } } }) } private fun makeEnqueteChoiceView( enquete : NicoEnquete, now : Long, i : Int, item : NicoEnquete.Choice ) { val canVote = if(access_info.isMisskey) { enquete.myVoted == null } else { val remain = enquete.time_start + NicoEnquete.ENQUETE_EXPIRE - now enquete.myVoted == null && remain > 0L } val lp = LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT ) if(i == 0) lp.topMargin = (0.5f + activity.density * 3f).toInt() val b = Button(activity) b.layoutParams = lp b.isAllCaps = false val text = if(access_info.isMisskey) { val sb = SpannableStringBuilder() .append(item.decoded_text) if(enquete.myVoted != null) { sb.append(" / ") sb.append(activity.getString(R.string.vote_count_text, item.votes)) if(i == enquete.myVoted) sb.append(' ').append(0x2713.toChar()) } sb } else { item.decoded_text } b.text = text val invalidator = NetworkEmojiInvalidator(activity.handler, b) extra_invalidator_list.add(invalidator) invalidator.register(text) if(! canVote) { b.isEnabled = false } else { val accessInfo = this@ItemViewHolder.access_info b.setOnClickListener { view -> val context = view.context ?: return@setOnClickListener onClickEnqueteChoice(enquete, context, accessInfo, i) } } llExtra.addView(b) } private fun makeEnqueteTimerView(enquete : NicoEnquete) { val density = activity.density val height = (0.5f + 6 * density).toInt() val view = EnqueteTimerView(activity) view.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, height) view.setParams(enquete.time_start, NicoEnquete.ENQUETE_EXPIRE) llExtra.addView(view) } private fun onClickEnqueteChoice( enquete : NicoEnquete, context : Context, accessInfo : SavedAccount, idx : Int ) { val now = System.currentTimeMillis() if(enquete.myVoted != null) { showToast(context, false, R.string.already_voted) return } if(! accessInfo.isMisskey) { val remain = enquete.time_start + NicoEnquete.ENQUETE_EXPIRE - now if(remain <= 0L) { showToast(context, false, R.string.enquete_was_end) return } } TootTaskRunner(context).run(accessInfo, object : TootTask { override fun background(client : TootApiClient) : TootApiResult? { return if(accessInfo.isMisskey) { client.request( "/api/notes/polls/vote", accessInfo.putMisskeyApiToken(JSONObject()) .put("noteId", enquete.status_id.toString()) .put("choice", idx) .toPostRequestBuilder() ) } else { client.request( "/api/v1/votes/${enquete.status_id}", JSONObject() .put("item_index", idx.toString()) .toPostRequestBuilder() ) } } override fun handleResult(result : TootApiResult?) { result ?: return // cancelled. val data = result.jsonObject if(data != null) { if(accessInfo.isMisskey) { if(enquete.increaseVote(activity, idx, true)) { showToast(context, false, R.string.enquete_voted) // 1個だけ開閉するのではなく、例えば通知TLにある複数の要素をまとめて開閉するなどある list_adapter.notifyChange(reason = "onClickEnqueteChoice", reset = true) } } else { val message = data.parseString("message") ?: "?" val valid = data.optBoolean("valid") if(valid) { showToast(context, false, R.string.enquete_voted) } else { showToast(context, true, R.string.enquete_vote_failed, message) } } } else { showToast(context, true, result.error) } } }) } private fun openFilterMenu(item : TootFilter) { val ad = ActionsDialog() ad.addAction(activity.getString(R.string.edit)) { ActKeywordFilter.open(activity, access_info, item.id) } ad.addAction(activity.getString(R.string.delete)) { Action_Filter.delete(activity, access_info, item) } ad.show(activity, activity.getString(R.string.filter_of, item.phrase)) } ///////////////////////////////////////////////////////////////////// private fun inflate(activity : ActMain) = with(activity.UI {}) { val b = Benchmark(log, "Item-Inflate", 40L) val rv = verticalLayout { // トップレベルのViewGroupのlparamsはイニシャライザ内部に置くしかないみたい layoutParams = RecyclerView.LayoutParams(matchParent, wrapContent).apply { marginStart = dip(8) marginEnd = dip(8) topMargin = dip(2f) bottomMargin = dip(1f) } setPaddingRelative(dip(4), dip(1f), dip(4), dip(2f)) descendantFocusability = ViewGroup.FOCUS_BLOCK_DESCENDANTS llBoosted = linearLayout { lparams(matchParent, wrapContent) { bottomMargin = dip(6) } background = ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent) gravity = Gravity.CENTER_VERTICAL ivBoosted = imageView { scaleType = ImageView.ScaleType.FIT_END importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO }.lparams(dip(48), dip(32)) { endMargin = dip(4) } verticalLayout { lparams(dip(0), wrapContent) { weight = 1f } linearLayout { lparams(matchParent, wrapContent) tvBoostedAcct = textView { ellipsize = TextUtils.TruncateAt.END gravity = Gravity.END maxLines = 1 textSize = 12f // textSize の単位はSP // tools:text ="who@hoge" }.lparams(dip(0), wrapContent) { weight = 1f } tvBoostedTime = textView { startPadding = dip(2) gravity = Gravity.END textSize = 12f // textSize の単位はSP // tools:ignore="RtlSymmetry" // tools:text="2017-04-16 09:37:14" }.lparams(wrapContent, wrapContent) } tvBoosted = textView { // tools:text = "~にブーストされました" }.lparams(matchParent, wrapContent) } } llFollow = linearLayout { lparams(matchParent, wrapContent) background = ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent) gravity = Gravity.CENTER_VERTICAL ivFollow = myNetworkImageView { contentDescription = context.getString(R.string.thumbnail) scaleType = ImageView.ScaleType.FIT_END }.lparams(dip(48), dip(40)) { endMargin = dip(4) } verticalLayout { lparams(dip(0), wrapContent) { weight = 1f } tvFollowerName = textView { // tools:text="Follower Name" }.lparams(matchParent, wrapContent) tvFollowerAcct = textView { setPaddingStartEnd(dip(4), dip(4)) textSize = 12f // SP // tools:text="aaaaaaaaaaaaaaaa" }.lparams(matchParent, wrapContent) } frameLayout { lparams(dip(40), dip(40)) { startMargin = dip(4) } btnFollow = imageButton { background = ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent) contentDescription = context.getString(R.string.follow) scaleType = ImageView.ScaleType.CENTER // tools:src="?attr/ic_follow_plus" }.lparams(matchParent, matchParent) ivFollowedBy = imageView { scaleType = ImageView.ScaleType.CENTER // tools:src="?attr/ic_followed_by" importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO }.lparams(matchParent, matchParent) } } llStatus = verticalLayout { lparams(matchParent, wrapContent) linearLayout { lparams(matchParent, wrapContent) tvAcct = textView { ellipsize = TextUtils.TruncateAt.END gravity = Gravity.END maxLines = 1 textSize = 12f // SP // tools:text="who@hoge" }.lparams(dip(0), wrapContent) { weight = 1f } tvTime = textView { gravity = Gravity.END startPadding = dip(2) textSize = 12f // SP // tools:ignore="RtlSymmetry" // tools:text="2017-04-16 09:37:14" }.lparams(wrapContent, wrapContent) } linearLayout { lparams(matchParent, wrapContent) ivThumbnail = myNetworkImageView { background = ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent) contentDescription = context.getString(R.string.thumbnail) scaleType = ImageView.ScaleType.CENTER_CROP }.lparams(dip(48), dip(48)) { topMargin = dip(4) endMargin = dip(4) } verticalLayout { lparams(dip(0), wrapContent) { weight = 1f } tvName = textView { }.lparams(matchParent, wrapContent) llInstanceTicker = linearLayout { lparams(matchParent, wrapContent) ivInstanceTicker = myNetworkImageView { }.lparams(dip(16), dip(16)) { isBaselineAligned = false } tvInstanceTicker = textView { setTextSize(TypedValue.COMPLEX_UNIT_DIP, 10f) gravity = Gravity.CENTER_VERTICAL setPaddingStartEnd(dip(4f), dip(4f)) }.lparams(0, dip(16)) { isBaselineAligned = false weight = 1f } } llReply = linearLayout { lparams(matchParent, wrapContent) { bottomMargin = dip(3) } background = ContextCompat.getDrawable( context, R.drawable.btn_bg_transparent ) gravity = Gravity.CENTER_VERTICAL ivReply = imageView { scaleType = ImageView.ScaleType.FIT_END importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO padding = dip(4) }.lparams(dip(32), dip(32)) { endMargin = dip(4) } tvReply = textView { }.lparams(dip(0), wrapContent) { weight = 1f } } llContentWarning = linearLayout { lparams(matchParent, wrapContent) { topMargin = dip(3) isBaselineAligned = false } gravity = Gravity.CENTER_VERTICAL btnContentWarning = button { background = ContextCompat.getDrawable(context, R.drawable.bg_button_cw) minWidthCompat = dip(40) padding = dip(4) //tools:text="見る" }.lparams(wrapContent, dip(40)) { endMargin = dip(8) } verticalLayout { lparams(dip(0), wrapContent) { weight = 1f } tvMentions = myTextView { }.lparams(matchParent, wrapContent) tvContentWarning = myTextView { }.lparams(matchParent, wrapContent) { topMargin = dip(3) } } } llContents = verticalLayout { lparams(matchParent, wrapContent) tvContent = myTextView { setLineSpacing(lineSpacingExtra, 1.1f) // tools:text="Contents\nContents" }.lparams(matchParent, wrapContent) { topMargin = dip(3) } val actMain = activity as? ActMain val thumbnailHeight = actMain?.app_state?.media_thumb_height ?: dip(64) val verticalArrangeThumbnails = Pref.bpVerticalArrangeThumbnails( actMain?.pref ?: Pref.pref(context) ) flMedia = if(verticalArrangeThumbnails) { frameLayout { lparams(matchParent, wrapContent) { topMargin = dip(3) } llMedia = verticalLayout { lparams(matchParent, matchParent) btnHideMedia = imageButton { background = ContextCompat.getDrawable( context, R.drawable.btn_bg_transparent ) contentDescription = "@string/hide" imageResource = R.drawable.ic_close }.lparams(dip(32), dip(32)) { gravity = Gravity.END } ivMedia1 = myNetworkImageView { background = ContextCompat.getDrawable( context, R.drawable.bg_thumbnail ) contentDescription = context.getString(R.string.thumbnail) scaleType = ImageView.ScaleType.CENTER_CROP }.lparams(matchParent, thumbnailHeight) { topMargin = dip(3) } ivMedia2 = myNetworkImageView { background = ContextCompat.getDrawable( context, R.drawable.bg_thumbnail ) contentDescription = context.getString(R.string.thumbnail) scaleType = ImageView.ScaleType.CENTER_CROP }.lparams(matchParent, thumbnailHeight) { topMargin = dip(3) } ivMedia3 = myNetworkImageView { background = ContextCompat.getDrawable( context, R.drawable.bg_thumbnail ) contentDescription = context.getString(R.string.thumbnail) scaleType = ImageView.ScaleType.CENTER_CROP }.lparams(matchParent, thumbnailHeight) { topMargin = dip(3) } ivMedia4 = myNetworkImageView { background = ContextCompat.getDrawable( context, R.drawable.bg_thumbnail ) contentDescription = context.getString(R.string.thumbnail) scaleType = ImageView.ScaleType.CENTER_CROP }.lparams(matchParent, thumbnailHeight) { topMargin = dip(3) } } btnShowMedia = textView { backgroundColor = getAttributeColor( context, R.attr.colorShowMediaBackground ) gravity = Gravity.CENTER_VERTICAL or Gravity.END text = context.getString(R.string.tap_to_show) textColor = getAttributeColor( context, R.attr.colorShowMediaText ) endPadding = dip(4) minHeightCompat = dip(32) }.lparams(matchParent, matchParent) } } else { frameLayout { lparams(matchParent, thumbnailHeight) { topMargin = dip(3) } llMedia = linearLayout { lparams(matchParent, matchParent) ivMedia1 = myNetworkImageView { background = ContextCompat.getDrawable( context, R.drawable.bg_thumbnail ) contentDescription = context.getString(R.string.thumbnail) scaleType = ImageView.ScaleType.CENTER_CROP }.lparams(0, matchParent) { weight = 1f } ivMedia2 = myNetworkImageView { background = ContextCompat.getDrawable( context, R.drawable.bg_thumbnail ) contentDescription = context.getString(R.string.thumbnail) scaleType = ImageView.ScaleType.CENTER_CROP }.lparams(0, matchParent) { startMargin = dip(8) weight = 1f } ivMedia3 = myNetworkImageView { background = ContextCompat.getDrawable( context, R.drawable.bg_thumbnail ) contentDescription = context.getString(R.string.thumbnail) scaleType = ImageView.ScaleType.CENTER_CROP }.lparams(0, matchParent) { startMargin = dip(8) weight = 1f } ivMedia4 = myNetworkImageView { background = ContextCompat.getDrawable( context, R.drawable.bg_thumbnail ) contentDescription = context.getString(R.string.thumbnail) scaleType = ImageView.ScaleType.CENTER_CROP }.lparams(0, matchParent) { startMargin = dip(8) weight = 1f } btnHideMedia = imageButton { background = ContextCompat.getDrawable( context, R.drawable.btn_bg_transparent ) contentDescription = "@string/hide" imageResource = R.drawable.ic_close }.lparams(dip(32), matchParent) { startMargin = dip(8) } } btnShowMedia = textView { backgroundColor = getAttributeColor( context, R.attr.colorShowMediaBackground ) gravity = Gravity.CENTER text = context.getString(R.string.tap_to_show) textColor = getAttributeColor( context, R.attr.colorShowMediaText ) }.lparams(matchParent, matchParent) } } tvMediaDescription = textView {}.lparams(matchParent, wrapContent) llCardOuter = verticalLayout { lparams(matchParent, wrapContent) { topMargin = dip(3) startMargin = dip(12) endMargin = dip(6) } padding = dip(3) bottomPadding = dip(6) background = PreviewCardBorder() tvCardText = myTextView { }.lparams(matchParent, wrapContent) { } ivCardImage = myNetworkImageView { contentDescription = context.getString(R.string.thumbnail) scaleType = if(Pref.bpDontCropMediaThumb(App1.pref)) ImageView.ScaleType.FIT_CENTER else ImageView.ScaleType.CENTER_CROP }.lparams(matchParent, activity.app_state.media_thumb_height) { topMargin = dip(3) } } llExtra = verticalLayout { lparams(matchParent, wrapContent) { topMargin = dip(0) } } } // button bar statusButtonsViewHolder = StatusButtonsViewHolder( activity , matchParent , 3f , justifyContent = when(Pref.ipBoostButtonJustify(App1.pref)) { 0 -> JustifyContent.FLEX_START 1 -> JustifyContent.CENTER else -> JustifyContent.FLEX_END } ) llButtonBar = statusButtonsViewHolder.viewRoot addView(llButtonBar) tvApplication = textView { gravity = Gravity.END }.lparams(matchParent, wrapContent) } } } llConversationIcons = linearLayout { lparams(matchParent, dip(40)) isBaselineAligned = false gravity = Gravity.START or Gravity.CENTER_VERTICAL tvConversationParticipants = textView { text = context.getString(R.string.participants) }.lparams(wrapContent, wrapContent) { endMargin = dip(3) } ivConversationIcon1 = myNetworkImageView { scaleType = ImageView.ScaleType.CENTER_CROP }.lparams(dip(24), dip(24)) { endMargin = dip(3) } ivConversationIcon2 = myNetworkImageView { scaleType = ImageView.ScaleType.CENTER_CROP }.lparams(dip(24), dip(24)) { endMargin = dip(3) } ivConversationIcon3 = myNetworkImageView { scaleType = ImageView.ScaleType.CENTER_CROP }.lparams(dip(24), dip(24)) { endMargin = dip(3) } ivConversationIcon4 = myNetworkImageView { scaleType = ImageView.ScaleType.CENTER_CROP }.lparams(dip(24), dip(24)) { endMargin = dip(3) } tvConversationIconsMore = textView { }.lparams(wrapContent, wrapContent) } llSearchTag = linearLayout { lparams(matchParent, wrapContent) btnSearchTag = button { background = ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent) allCaps = false }.lparams(matchParent, wrapContent) } llTrendTag = linearLayout { lparams(matchParent, wrapContent) gravity = Gravity.CENTER_VERTICAL background = ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent) verticalLayout { lparams(0, wrapContent) { weight = 1f } tvTrendTagName = textView { }.lparams(matchParent, wrapContent) tvTrendTagDesc = textView { textSize = 12f // SP }.lparams(matchParent, wrapContent) } tvTrendTagCount = textView { }.lparams(wrapContent, wrapContent) { startMargin = dip(6) endMargin = dip(6) } cvTrendTagHistory = trendTagHistoryView { }.lparams(dip(64), dip(32)) } llList = linearLayout { lparams(matchParent, wrapContent) btnListTL = button { background = ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent) allCaps = false }.lparams(0, wrapContent) { weight = 1f } btnListMore = imageButton { background = ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent) imageResource = R.drawable.ic_more contentDescription = context.getString(R.string.more) }.lparams(dip(40), dip(40)) { startMargin = dip(4) } } tvMessageHolder = textView { padding = dip(4) }.lparams(matchParent, wrapContent) llFollowRequest = linearLayout { lparams(matchParent, wrapContent) { topMargin = dip(6) } gravity = Gravity.END btnFollowRequestAccept = imageButton { background = ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent) contentDescription = context.getString(R.string.follow_accept) imageResource = R.drawable.ic_check setPadding(0, 0, 0, 0) }.lparams(dip(48f), dip(32f)) btnFollowRequestDeny = imageButton { background = ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent) contentDescription = context.getString(R.string.follow_deny) imageResource = R.drawable.ic_close setPadding(0, 0, 0, 0) }.lparams(dip(48f), dip(32f)) { startMargin = dip(4) } } llFilter = verticalLayout { lparams(matchParent, wrapContent) { } minimumHeight = dip(40) tvFilterPhrase = textView { typeface = Typeface.DEFAULT_BOLD }.lparams(matchParent, wrapContent) tvFilterDetail = textView { textSize = 12f // SP }.lparams(matchParent, wrapContent) } } b.report() rv } }