SubwayTooter-Android-App/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.kt

3838 lines
107 KiB
Kotlin
Raw Normal View History

package jp.juggler.subwaytooter
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Typeface
import android.os.SystemClock
2019-02-15 02:51:22 +01:00
import androidx.core.content.ContextCompat
import androidx.appcompat.app.AlertDialog
2018-11-18 03:38:52 +01:00
import android.text.Spannable
import android.text.SpannableString
import android.text.SpannableStringBuilder
import android.text.TextUtils
2019-05-05 06:41:13 +02:00
import android.text.style.BackgroundColorSpan
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.*
2019-01-06 15:55:25 +01:00
import jp.juggler.subwaytooter.api.*
import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.dialog.ActionsDialog
import jp.juggler.subwaytooter.dialog.DlgConfirm
2019-03-06 10:49:02 +01:00
import jp.juggler.subwaytooter.drawable.PollPlotDrawable
import jp.juggler.subwaytooter.drawable.PreviewCardBorder
import jp.juggler.subwaytooter.span.MyClickableSpan
import jp.juggler.subwaytooter.span.NetworkEmojiSpan
import jp.juggler.subwaytooter.table.*
import jp.juggler.subwaytooter.util.*
import jp.juggler.subwaytooter.view.*
2018-12-01 00:02:18 +01:00
import jp.juggler.util.*
import org.jetbrains.anko.*
2018-11-18 03:38:52 +01:00
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 : BlurhashView
private lateinit var ivMedia1 : MyNetworkImageView
private lateinit var ivMedia2 : MyNetworkImageView
private lateinit var ivMedia3 : MyNetworkImageView
private lateinit var ivMedia4 : MyNetworkImageView
2018-11-18 03:38:52 +01:00
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 cvTagHistory : TagHistoryView
private lateinit var llList : View
private lateinit var btnListTL : Button
private lateinit var btnListMore : ImageButton
private lateinit var llFollowRequest : View
private lateinit var btnFollowRequestAccept : ImageButton
private lateinit var btnFollowRequestDeny : ImageButton
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 flCardImage : View
private lateinit var llCardImage : View
private lateinit var ivCardImage : MyNetworkImageView
private lateinit var btnCardImageHide : ImageButton
private lateinit var btnCardImageShow : Button
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 tvLastStatusAt : 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
2018-11-18 03:38:52 +01:00
private var content_color : Int = 0
private var acct_color : Int = 0
private var content_color_csl : ColorStateList = ColorStateList.valueOf(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 lastActive_invalidator : NetworkEmojiInvalidator
private val extra_invalidator_list = ArrayList<NetworkEmojiInvalidator>()
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)
btnCardImageHide.setOnClickListener(this)
btnCardImageShow.setOnClickListener(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
2018-11-18 03:38:52 +01:00
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
btnCardImageShow.textSize = f
2018-11-18 03:38:52 +01:00
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
}
2018-11-18 03:38:52 +01:00
f = activity.notification_tl_font_size_sp
if(! f.isNaN()) {
tvBoosted.textSize = f
tvReply.textSize = f
}
2018-11-18 03:38:52 +01:00
f = activity.acct_font_size_sp
if(! f.isNaN()) {
tvBoostedAcct.textSize = f
tvBoostedTime.textSize = f
tvFollowerAcct.textSize = f
tvLastStatusAt.textSize = f
2018-11-18 03:38:52 +01:00
tvAcct.textSize = f
tvTime.textSize = f
tvTrendTagDesc.textSize = f
tvFilterDetail.textSize = f
}
val spacing = activity.timeline_spacing
if(spacing != null) {
tvFollowerName.setLineSpacing(0f, spacing)
tvName.setLineSpacing(0f, spacing)
tvMentions.setLineSpacing(0f, spacing)
tvContentWarning.setLineSpacing(0f, spacing)
tvContent.setLineSpacing(0f, spacing)
btnShowMedia.setLineSpacing(0f, spacing)
btnCardImageShow.setLineSpacing(0f, spacing)
tvApplication.setLineSpacing(0f, spacing)
tvMessageHolder.setLineSpacing(0f, spacing)
btnListTL.setLineSpacing(0f, spacing)
tvTrendTagName.setLineSpacing(0f, spacing)
tvTrendTagCount.setLineSpacing(0f, spacing)
tvFilterPhrase.setLineSpacing(0f, spacing)
tvMediaDescription.setLineSpacing(0f, spacing)
tvCardText.setLineSpacing(0f, spacing)
tvConversationIconsMore.setLineSpacing(0f, spacing)
tvConversationParticipants.setLineSpacing(0f, spacing)
tvBoosted.setLineSpacing(0f, spacing)
tvReply.setLineSpacing(0f, spacing)
}
2018-11-18 03:38:52 +01:00
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
2018-11-18 03:38:52 +01:00
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)
this.lastActive_invalidator = NetworkEmojiInvalidator(activity.handler, tvLastStatusAt)
val cardBackground = llCardOuter.background
if(cardBackground is PreviewCardBorder) {
val density = activity.density
cardBackground.round = (density * 8f)
cardBackground.width = (density * 1f)
}
2019-05-05 06:41:13 +02:00
val textShowMedia = SpannableString(activity.getString(R.string.tap_to_show))
.apply {
val colorBg = getAttributeColor(activity, R.attr.colorShowMediaBackground)
.applyAlphaMultiplier(0.5f)
setSpan(
BackgroundColorSpan(colorBg),
0,
this.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
btnShowMedia.text = textShowMedia
btnCardImageShow.text = textShowMedia
}
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
2018-08-22 03:05:54 +02:00
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 ||
2018-08-22 03:05:54 +02:00
v === tvTrendTagCount ||
v === tvTrendTagName ||
v === tvConversationIconsMore ||
v === tvConversationParticipants ||
2018-08-22 03:05:54 +02:00
v === tvFilterPhrase -> font_bold
else -> font_normal
}
}
2018-08-22 03:05:54 +02:00
} 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
flCardImage.visibility = View.GONE
llConversationIcons.visibility = View.GONE
removeExtraView()
var c : Int
c = column.getContentColor()
2018-11-18 03:38:52 +01:00
this.content_color = c
this.content_color_csl = ColorStateList.valueOf(c)
2018-11-18 03:38:52 +01:00
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)
cvTagHistory.setColor(c)
tvFilterPhrase.setTextColor(c)
tvMediaDescription.setTextColor(c)
tvCardText.setTextColor(c)
tvConversationIconsMore.setTextColor(c)
tvConversationParticipants.setTextColor(c)
2018-11-18 03:38:52 +01:00
(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)
2018-11-18 03:38:52 +01:00
tvFilterDetail.setTextColor(c)
tvFilterPhrase.setTextColor(c)
2018-11-18 03:38:52 +01:00
// 以下のビューの文字色は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
val colorBg = Pref.ipEventBgColorBoost(activity.pref)
showReply(reblog, R.drawable.ic_repeat, R.string.renote_to)
showStatus(item, colorBg)
}
else -> {
// 引用なしブースト
val colorBg = Pref.ipEventBgColorBoost(activity.pref)
showBoost(
item.accountRef,
item.time_created_at,
R.drawable.ic_repeat,
R.string.display_name_boosted_by,
boost_status = item
)
showStatusOrReply(item.reblog, colorBg)
}
}
}
is TootAccountRef -> showAccount(item)
is TootNotification -> showNotification(item)
is TootGap -> showGap()
is TootSearchGap -> showSearchGap(item)
is TootDomainBlock -> showDomainBlock(item)
is TootList -> showList(item)
is TootMessageHolder -> showMessageHolder(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) {
2019-01-06 15:55:25 +01:00
try {
llStatus.visibility = View.VISIBLE
this.viewRoot.setBackgroundColor(0)
showStatusTimeScheduled(activity, tvTime, item)
val who = access_info.loginAccount !!
2019-01-06 15:55:25 +01:00
val whoRef = TootAccountRef(TootParser(activity, access_info), who)
this.status_account = whoRef
2019-01-06 15:55:25 +01:00
setAcct(tvAcct, access_info, who)
2019-01-06 15:55:25 +01:00
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 ?: "")
2019-01-06 15:55:25 +01:00
tvMentions.visibility = View.GONE
tvContent.text = content
content_invalidator.register(content)
tvContent.minLines = - 1
2019-01-06 15:55:25 +01:00
val decoded_spoiler_text = SpannableString(item.spoiler_text ?: "")
2019-01-06 15:55:25 +01:00
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, access_info.expand_cw)
2019-01-06 15:55:25 +01:00
showContent(cw_shown)
}
else -> {
// CWしない
llContentWarning.visibility = View.GONE
llContents.visibility = View.VISIBLE
}
}
val media_attachments = item.media_attachments
if(media_attachments?.isEmpty() != false) {
2019-01-06 15:55:25 +01:00
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)
2019-01-06 15:55:25 +01:00
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(
2019-01-06 15:55:25 +01:00
activity,
btnHideMedia,
R.drawable.ic_close,
2019-01-06 15:55:25 +01:00
color = content_color,
alphaMultiplier = Styler.boost_alpha
)
}
buttons_for_status?.hide()
tvApplication.visibility = View.GONE
} catch(ex : Throwable) {
2019-01-06 15:55:25 +01:00
}
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) {
2018-11-11 13:17:36 +01:00
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, colorBgArg : Int = 0) {
var colorBg = colorBgArg
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 {
2019-03-06 10:49:02 +01:00
reply != null -> {
showReply(reply, R.drawable.ic_reply, R.string.reply_to)
2019-03-06 10:49:02 +01:00
if(colorBgArg == 0) colorBg = Pref.ipEventBgColorMention(activity.pref)
}
2018-11-18 03:38:52 +01:00
in_reply_to_id != null && in_reply_to_account_id != null -> {
showReply(item, in_reply_to_account_id)
2019-03-06 10:49:02 +01:00
if(colorBgArg == 0) colorBg = Pref.ipEventBgColorMention(activity.pref)
}
}
showStatus(item, colorBg)
}
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
2018-05-18 19:08:46 +02:00
val n_account = n_accountRef?.get()
fun showNotificationStatus(item : TootStatus, colorBgDefault : Int) {
val reblog = item.reblog
when {
reblog == null -> showStatusOrReply(item, colorBgDefault)
2018-11-18 03:38:52 +01:00
! item.hasAnyContent() -> {
// 通常のブースト。引用なしブースト。
// ブースト表示は通知イベントと被るのでしない
showStatusOrReply(reblog, Pref.ipEventBgColorBoost(activity.pref))
2018-11-18 03:38:52 +01:00
}
else -> {
// 引用Renote
showReply(reblog, R.drawable.ic_repeat, R.string.renote_to)
showStatus(item, Pref.ipEventBgColorQuote(activity.pref))
}
}
}
when(n.type) {
TootNotification.TYPE_FAVOURITE -> {
val colorBg = Pref.ipEventBgColorFavourite(activity.pref)
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, colorBg)
}
}
TootNotification.TYPE_REBLOG -> {
val colorBg = Pref.ipEventBgColorBoost(activity.pref)
if(n_account != null) showBoost(
n_accountRef,
n.time_created_at,
R.drawable.ic_repeat,
R.string.display_name_boosted_by,
boost_status = n_status
)
if(n_status != null) {
showNotificationStatus(n_status, colorBg)
}
}
TootNotification.TYPE_RENOTE -> {
// 引用のないreblog
val colorBg = Pref.ipEventBgColorBoost(activity.pref)
if(n_account != null) showBoost(
n_accountRef,
n.time_created_at,
R.drawable.ic_repeat,
R.string.display_name_boosted_by,
boost_status = n_status
)
if(n_status != null) {
showNotificationStatus(n_status, colorBg)
}
}
TootNotification.TYPE_FOLLOW -> {
val colorBg = Pref.ipEventBgColorFollow(activity.pref)
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)
if(colorBg != 0) this.viewRoot.backgroundColor = colorBg
}
}
TootNotification.TYPE_UNFOLLOW -> {
val colorBg = Pref.ipEventBgColorUnfollow(activity.pref)
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)
if(colorBg != 0) this.viewRoot.backgroundColor = colorBg
}
}
2018-11-18 03:38:52 +01:00
TootNotification.TYPE_MENTION,
TootNotification.TYPE_REPLY -> {
val colorBg = Pref.ipEventBgColorMention(activity.pref)
if(! bSimpleList && ! access_info.isMisskey) {
when {
n_account == null -> {
}
n_status?.in_reply_to_id != null || n_status?.reply != null -> {
2018-11-18 03:38:52 +01:00
// トゥート内部に「~への返信」を表示するので、
// 通知イベントの「~からの返信」は表示しない
}
else -> // 返信ではなくメンションの場合は「~からの返信」を表示する
2018-11-18 03:38:52 +01:00
showBoost(
n_accountRef,
n.time_created_at,
R.drawable.ic_reply,
R.string.display_name_mentioned_by
2018-11-18 03:38:52 +01:00
)
}
}
if(n_status != null) {
showNotificationStatus(n_status, colorBg)
}
}
TootNotification.TYPE_REACTION -> {
val colorBg = Pref.ipEventBgColorReaction(activity.pref)
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,
misskeyReaction = reaction
)
if(n_status != null) {
showNotificationStatus(n_status, colorBg)
}
}
TootNotification.TYPE_QUOTE -> {
val colorBg = Pref.ipEventBgColorQuote(activity.pref)
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, colorBg)
}
}
TootNotification.TYPE_FOLLOW_REQUEST,
TootNotification.TYPE_FOLLOW_REQUEST_MISSKEY -> {
val colorBg = Pref.ipEventBgColorFollowRequest(activity.pref)
if(n_account != null) {
showBoost(
n_accountRef,
n.time_created_at,
R.drawable.ic_follow_wait,
R.string.display_name_follow_request_by
)
if(colorBg != 0) this.viewRoot.backgroundColor = colorBg
boostedAction = {
activity.addColumn(
activity.nextPosition(column)
, access_info
, ColumnType.FOLLOW_REQUESTS
)
}
}
}
TootNotification.TYPE_FOLLOW_REQUEST_ACCEPTED_MISSKEY -> {
val colorBg = Pref.ipEventBgColorFollow(activity.pref)
if(n_account != null) {
showBoost(
n_accountRef,
n.time_created_at,
R.drawable.ic_follow_plus,
R.string.display_name_follow_request_accepted_by
)
showAccount(n_accountRef)
if(colorBg != 0) this.viewRoot.backgroundColor = colorBg
}
}
TootNotification.TYPE_VOTE,
TootNotification.TYPE_POLL_VOTE_MISSKEY -> {
val colorBg = Pref.ipEventBgColorVote(activity.pref)
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, colorBg)
}
}
TootNotification.TYPE_POLL -> {
val colorBg = 0
if(n_account != null) showBoost(
n_accountRef,
n.time_created_at,
R.drawable.ic_vote,
R.string.end_of_polling_from
)
if(n_status != null) {
showNotificationStatus(n_status, colorBg)
}
}
else -> {
val colorBg = 0
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, colorBg)
}
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
btnListTL.textColor = content_color
btnListMore.imageTintList = content_color_csl
}
private fun showDomainBlock(domain_block : TootDomainBlock) {
llSearchTag.visibility = View.VISIBLE
btnSearchTag.text = domain_block.domain.pretty
}
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<String>()
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) {
if(tag.history?.isNotEmpty() == true) {
llTrendTag.visibility = View.VISIBLE
tvTrendTagName.text = "#${tag.name}"
tvTrendTagDesc.text =
activity.getString(R.string.people_talking, tag.accountDaily, tag.accountWeekly)
tvTrendTagCount.text = "${tag.countDaily}(${tag.countWeekly})"
cvTagHistory.setHistory(tag.history)
} else {
llSearchTag.visibility = View.VISIBLE
btnSearchTag.text = "#" + tag.name
}
}
private fun showGap() {
llSearchTag.visibility = View.VISIBLE
btnSearchTag.text = activity.getString(R.string.read_gap)
}
2019-08-23 01:49:20 +02:00
private fun showSearchGap(item : TootSearchGap) {
llSearchTag.visibility = View.VISIBLE
2019-08-23 01:49:20 +02:00
btnSearchTag.text = activity.getString(
when(item.type) {
TootSearchGap.SearchType.Hashtag -> R.string.read_more_hashtag
TootSearchGap.SearchType.Account -> R.string.read_more_account
TootSearchGap.SearchType.Status -> R.string.read_more_status
}
)
}
private fun showReply(iconId : Int, text : Spannable) {
llReply.visibility = View.VISIBLE
2018-11-18 03:38:52 +01:00
setIconDrawableId(
2018-11-18 03:38:52 +01:00
activity,
ivReply,
iconId,
color = content_color,
alphaMultiplier = Styler.boost_alpha
2018-11-18 03:38:52 +01:00
)
tvReply.text = text
reply_invalidator.register(text)
}
private fun showReply(reply : TootStatus, iconId : Int, stringId : Int) {
status_reply = reply
showReply(
iconId,
reply.accountRef.decoded_display_name.intoStringResource(activity, stringId)
)
}
private fun showReply(reply : TootStatus, accountId : EntityId) {
val name = if(accountId == reply.account.id) {
// 自己レスなら
AcctColor.getNicknameWithColor(access_info, reply.account)
2018-11-18 03:38:52 +01:00
} else {
val m = reply.mentions?.find { it.id == accountId }
2018-11-18 03:38:52 +01:00
if(m != null) {
AcctColor.getNicknameWithColor(access_info.getFullAcct(m.acct))
2018-11-18 03:38:52 +01:00
} else {
SpannableString("ID(${accountId})")
}
}
val text = name.intoStringResource(activity, R.string.reply_to)
showReply(R.drawable.ic_reply, text)
2019-09-23 19:08:38 +02:00
// tootsearchはreplyオブジェクトがなくin_reply_toだけが提供される場合があるが
// tootsearchではどのタンスから読んだか分からないのでin_reply_toのIDも信用できない
}
private fun showBoost(
whoRef : TootAccountRef,
time : Long,
iconId : Int,
string_id : Int,
misskeyReaction : MisskeyReaction? = null,
boost_status : TootStatus? = null
) {
boost_account = whoRef
2018-05-18 19:08:46 +02:00
val who = whoRef.get()
// フォローの場合 decoded_display_name が2箇所で表示に使われるのを避ける必要がある
val text : Spannable =who.decodeDisplayName(activity)
.intoStringResource(activity, string_id)
2020-01-11 03:55:31 +01:00
val emojiResource = misskeyReaction?.emojiResource
2020-01-10 14:42:37 +01:00
if(emojiResource != null) {
emojiResource.loadToImageView(activity, ivBoosted)
// TODO パディング少し変える?
} else {
setIconDrawableId(
2018-11-18 03:38:52 +01:00
activity,
ivBoosted,
iconId,
color = content_color,
alphaMultiplier = Styler.boost_alpha
2018-11-18 03:38:52 +01:00
)
}
boost_time = time
llBoosted.visibility = View.VISIBLE
showStatusTime(activity, tvBoostedTime, who, time = time, status = boost_status)
tvBoosted.text = text
boost_invalidator.register(text)
setAcct(tvBoostedAcct, access_info, who)
}
private fun showAccount(whoRef : TootAccountRef) {
follow_account = whoRef
2018-05-18 19:08:46 +02:00
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, who)
who.setAccountExtra(access_info, tvLastStatusAt, lastActive_invalidator)
val relation = UserRelation.load(access_info.db_id, who.id)
Styler.setFollowIcon(
activity,
btnFollow,
ivFollowedBy,
relation,
who,
content_color,
alphaMultiplier = Styler.boost_alpha
)
2019-08-23 01:49:20 +02:00
if(column.type == ColumnType.FOLLOW_REQUESTS) {
llFollowRequest.visibility = View.VISIBLE
btnFollowRequestAccept.imageTintList = content_color_csl
btnFollowRequestDeny.imageTintList = content_color_csl
}
}
private fun showStatus(status : TootStatus, colorBg : Int = 0) {
val filteredWord = status.filteredWord
if(filteredWord != null) {
showMessageHolder(
TootMessageHolder(
if(Pref.bpShowFilteredWord(activity.pref)) {
"${activity.getString(R.string.filtered)} / $filteredWord"
} else {
activity.getString(R.string.filtered)
}
)
)
return
}
this.status_showing = status
llStatus.visibility = View.VISIBLE
if(status.conversation_main) {
this.viewRoot.setBackgroundColor(
2018-12-01 00:02:18 +01:00
(getAttributeColor(
activity,
R.attr.colorImageButtonAccent
) and 0xffffff) or 0x20000000
)
} else {
val c = colorBg.notZero()
?: 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.backgroundColor = c
}
}
showStatusTime(activity, tvTime, who = status.account, status = status)
val whoRef = status.accountRef
2018-05-18 19:08:46 +02:00
val who = whoRef.get()
this.status_account = whoRef
setAcct(tvAcct, access_info, who)
// 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
when {
enquete == null -> {
}
enquete.pollType == TootPollsType.FriendsNico && enquete.type != TootPolls.TYPE_ENQUETE -> {
2019-03-06 10:49:02 +01:00
// フレニコの投票の結果表示は普通にテキストを表示するだけでよい
}
else -> {
2018-08-24 18:24:11 +02:00
2019-03-06 10:49:02 +01:00
// アンケートの本文を上書きする
val question = enquete.decoded_question
2018-08-24 18:24:11 +02:00
if(question.isNotBlank()) content = question
2019-03-06 10:49:02 +01:00
showEnqueteItems(status, 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, access_info.expand_cw)
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, access_info.expand_cw)
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)
2019-08-23 01:49:20 +02:00
val m0 =
if(media_attachments.isEmpty()) null else media_attachments[0] as? TootAttachment
btnShowMedia.blurhash = m0?.blurhash
2019-05-05 06:41:13 +02:00
if(sb.isNotEmpty()) {
tvMediaDescription.visibility = View.VISIBLE
tvMediaDescription.text = sb
}
2018-11-18 03:38:52 +01:00
setIconDrawableId(
2018-11-18 03:38:52 +01:00
activity,
btnHideMedia,
R.drawable.ic_close,
color = content_color,
alphaMultiplier = Styler.boost_alpha
2018-11-18 03:38:52 +01:00
)
}
makeReactionsView(status)
buttons_for_status?.bind(status, (item as? TootNotification))
var sb : StringBuilder? = null
fun prepareSb() : StringBuilder =
sb?.append(", ") ?: StringBuilder().also { sb = it }
val application = status.application
if(application != null &&
(column.type == ColumnType.CONVERSATION || Pref.bpShowAppName(activity.pref))
) {
prepareSb().append(activity.getString(R.string.application_is, application.name ?: ""))
}
val language = status.language
if(language != null &&
(column.type == ColumnType.CONVERSATION || Pref.bpShowLanguage(activity.pref))
) {
prepareSb().append(activity.getString(R.string.language_is, language))
}
tvApplication.vg(sb != null)?.text = sb
}
private fun showInstanceTicker(who : TootAccount) {
try {
if(! Column.useInstanceTicker) return
val host = who.host
// LTLでホスト名が同じならTickerを表示しない
2019-08-23 01:49:20 +02:00
when(column.type) {
ColumnType.LOCAL, ColumnType.LOCAL_AROUND -> {
if(host == access_info.host) return
}
2019-08-23 01:49:20 +02:00
else -> {
}
}
val item = InstanceTicker.lastList[host.ascii] ?: 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(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")
2018-08-21 01:20:39 +02:00
}
// 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')
2018-11-18 03:38:52 +01:00
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')
2018-11-18 03:38:52 +01:00
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')
sb.appendColorShadeIcon(
activity,
R.drawable.ic_unread,
"unread",
color = MyClickableSpan.defaultLinkColor
)
}
}
if(sb.isNotEmpty()) sb.append(' ')
sb.append(
when {
time != null -> TootStatus.formatTime(
activity,
time,
2019-08-23 01:49:20 +02:00
column.type != ColumnType.CONVERSATION
)
status != null -> TootStatus.formatTime(
activity,
status.time_created_at,
2019-08-23 01:49:20 +02:00
column.type != ColumnType.CONVERSATION
)
else -> "?"
}
)
tv.text = sb
}
2019-01-06 15:55:25 +01:00
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(
2019-01-06 15:55:25 +01:00
activity,
access_info.isMisskey,
item.visibility
2019-01-06 15:55:25 +01:00
)
)
}
2019-01-06 15:55:25 +01:00
if(sb.isNotEmpty()) sb.append(' ')
sb.append(
TootStatus.formatTime(
activity,
item.timeScheduledAt,
2019-08-23 01:49:20 +02:00
column.type != ColumnType.CONVERSATION
2019-01-06 15:55:25 +01:00
)
)
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, accessInfo : SavedAccount, who : TootAccount) {
val ac = AcctColor.load(accessInfo, who)
tv.text = when {
AcctColor.hasNickname(ac) -> ac.nickname
Pref.bpShortAcctLocalUser(App1.pref) -> "@${who.acct.pretty}"
else -> "@${ac.nickname}"
}
tv.textColor = ac.color_fg.notZero() ?: this.acct_color
tv.setBackgroundColor(ac.color_bg) // may 0
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<TootAttachmentLike>,
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()
}
val showUrl : Boolean
when(ta.type) {
TootAttachmentType.Audio -> {
iv.setMediaType(0)
iv.setDefaultImage(defaultColorIcon(activity, R.drawable.wide_music))
iv.setImageUrl(activity.pref, 0f, null)
showUrl = true
}
TootAttachmentType.Unknown -> {
iv.setMediaType(0)
iv.setDefaultImage(defaultColorIcon(activity, R.drawable.wide_question))
iv.setImageUrl(activity.pref, 0f, null)
showUrl = true
}
else -> when(val urlThumbnail = ta.urlForThumbnail) {
null, "" -> {
iv.setMediaType(0)
iv.setDefaultImage(defaultColorIcon(activity, R.drawable.wide_question))
iv.setImageUrl(activity.pref, 0f, null)
showUrl = true
}
else -> {
iv.setMediaType(
when(ta.type) {
TootAttachmentType.Video -> R.drawable.media_type_video
TootAttachmentType.GIFV -> R.drawable.media_type_gifv
else -> 0
}
)
iv.setDefaultImage(null)
iv.setImageUrl(
activity.pref,
0f,
access_info.supplyBaseUrl(urlThumbnail),
access_info.supplyBaseUrl(urlThumbnail)
)
showUrl = false
}
}
}
fun appendDescription(s : String) {
// 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(sbDesc.isNotEmpty()) sbDesc.append("\n")
val desc = activity.getString(R.string.media_description, idx + 1, s)
sbDesc.append(desc)
}
2019-12-22 14:06:03 +01:00
when(val description = ta.description.notEmpty()) {
null -> if(showUrl) ta.urlForDescription.notEmpty()?.let { appendDescription(it) }
else -> appendDescription(description)
}
}
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, btnCardImageHide -> {
fun hideViews() {
llMedia.visibility = View.GONE
btnShowMedia.visibility = View.VISIBLE
llCardImage.visibility = View.GONE
btnCardImageShow.visibility = View.VISIBLE
}
2019-01-06 15:55:25 +01:00
status_showing?.let { status ->
MediaShown.save(status, false)
hideViews()
2019-01-06 15:55:25 +01:00
}
if(item is TootScheduled) {
2019-01-06 15:55:25 +01:00
MediaShown.save(item.uri, false)
hideViews()
2019-01-06 15:55:25 +01:00
}
}
btnShowMedia, btnCardImageShow -> {
fun showViews() {
llMedia.visibility = View.VISIBLE
btnShowMedia.visibility = View.GONE
llCardImage.visibility = View.VISIBLE
btnCardImageShow.visibility = View.GONE
}
2019-01-06 15:55:25 +01:00
status_showing?.let { status ->
MediaShown.save(status, true)
showViews()
2019-01-06 15:55:25 +01:00
}
if(item is TootScheduled) {
2019-01-06 15:55:25 +01:00
MediaShown.save(item.uri, true)
showViews()
2019-01-06 15:55:25 +01:00
}
}
ivMedia1 -> clickMedia(0)
ivMedia2 -> clickMedia(1)
ivMedia3 -> clickMedia(2)
ivMedia4 -> clickMedia(3)
btnContentWarning -> {
2019-01-06 15:55:25 +01:00
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) {
2019-01-06 15:55:25 +01:00
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
when {
s != null -> Action_Toot.conversation(activity, pos, access_info, s)
// tootsearchは返信元のIDを取得するのにひと手間必要
column.type == ColumnType.SEARCH_TS ->
Action_Toot.showReplyTootsearch(activity, pos, status_showing)
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 {
2018-05-18 19:08:46 +02:00
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 TootSearchGap -> column.startGap(item)
is TootDomainBlock -> {
AlertDialog.Builder(activity)
.setMessage(
activity.getString(
R.string.confirm_unblock_domain,
item.domain.pretty
)
)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.ok) { _, _ ->
Action_Instance.blockDomain(
activity,
access_info,
item.domain,
bBlock = false
)
}
.show()
}
is TootTag -> {
Action_HashTag.timeline(
activity,
activity.nextPosition(column),
access_info,
item.name // #を含まない
)
}
is TootScheduled -> {
ActionsDialog()
2019-11-11 13:34:50 +01:00
.addAction(activity.getString(R.string.edit)) {
Action_Toot.editScheduledPost(activity, access_info, item)
}
.addAction(activity.getString(R.string.delete)) {
Action_Toot.deleteScheduledPost(activity, access_info, item) {
column.onScheduleDeleted(item)
showToast(activity, false, R.string.scheduled_post_deleted)
}
}
.show(activity)
}
}
btnListTL -> if(item is TootList) {
2019-08-23 01:49:20 +02:00
activity.addColumn(pos, access_info, ColumnType.LIST_TL, item.id)
}
btnListMore -> if(item is TootList) {
ActionsDialog()
.addAction(activity.getString(R.string.list_timeline)) {
2019-08-23 01:49:20 +02:00
activity.addColumn(pos, access_info, ColumnType.LIST_TL, item.id)
}
.addAction(activity.getString(R.string.list_member)) {
activity.addColumn(
false,
pos,
access_info,
2019-08-23 01:49:20 +02:00
ColumnType.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 ->
2018-05-18 19:08:46 +02:00
val who = whoRef.get()
DlgConfirm.openSimple(
activity,
activity.getString(
R.string.follow_accept_confirm,
AcctColor.getNickname(access_info, who)
)
) {
Action_Follow.authorizeFollowRequest(activity, access_info, whoRef, true)
}
}
btnFollowRequestDeny -> follow_account?.let { whoRef ->
2018-05-18 19:08:46 +02:00
val who = whoRef.get()
DlgConfirm.openSimple(
activity,
activity.getString(
R.string.follow_deny_confirm,
AcctColor.getNickname(access_info, 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
when {
// 返信元のstatusがあるならコンテキストメニュー
s != null -> DlgContextMenu(
activity,
column,
s.accountRef,
s,
notification,
tvContent
).show()
// それ以外はコンテキストメニューではなく会話を開く
// tootsearchは返信元のIDを取得するのにひと手間必要
column.type == ColumnType.SEARCH_TS ->
Action_Toot.showReplyTootsearch(
2018-11-18 03:38:52 +01:00
activity,
activity.nextPosition(column),
status_showing
2018-11-18 03:38:52 +01:00
)
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,
2018-05-18 19:08:46 +02:00
whoRef.get()
)
}
return true
}
ivCardImage -> Action_Toot.conversationOtherInstance(
activity,
activity.nextPosition(column),
status_showing?.card?.originalStatus
)
btnSearchTag, llTrendTag -> {
when(val item = this.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 url = "https://${access_info.host.ascii}/tags/$tagEncoded"
Action_HashTag.timelineOtherInstance(
activity = activity,
pos = activity.nextPosition(column),
url = url,
host = access_info.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
when(val item = if(i < media_attachments.size) media_attachments[i] else return) {
is TootAttachmentMSP -> {
// マストドン検索ポータルのデータではmedia_attachmentsが簡略化されている
// 会話の流れを表示する
Action_Toot.conversationOtherInstance(
activity,
activity.nextPosition(column),
status_showing
)
}
is TootAttachment -> when {
// unknownが1枚だけなら内蔵ビューアを使わずにインテントを投げる
2019-12-22 14:06:03 +01:00
item.type == TootAttachmentType.Unknown && media_attachments.size == 1 -> {
// https://github.com/tateisu/SubwayTooter/pull/119
// メディアタイプがunknownの場合、そのほとんどはリモートから来たURLである
// Pref.bpPriorLocalURL の状態に関わらずリモートURLがあればそれをブラウザで開く
when(val remoteUrl = item.remote_url.notEmpty()) {
null -> App1.openCustomTab(activity, item)
else -> App1.openCustomTab(activity, remoteUrl)
}
}
// 内蔵メディアビューアを使う
Pref.bpUseInternalMediaViewer(App1.pref) ->
ActMediaViewer.open(
activity,
when(access_info.isMisskey) {
true -> ServiceType.MISSKEY
else -> ServiceType.MASTODON
},
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
// 会話カラムで返信ステータスなら捏造したカードを表示しない
2019-08-23 01:49:20 +02:00
if(column.type == ColumnType.CONVERSATION
&& card.originalStatus != null
&& status.reply != null
) {
return
}
var bShowOuter = 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("<br>")
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
bShowOuter = true
}
}
val image = card.image
if(flCardImage.vg(image?.isNotEmpty() == true) != null) {
flCardImage.layoutParams.height = if(card.originalStatus != null) {
activity.avatarIconSize
} else {
activity.app_state.media_thumb_height
}
val imageUrl = access_info.supplyBaseUrl(image)
ivCardImage.setImageUrl(activity.pref, 0f, imageUrl, imageUrl)
// show about card outer
bShowOuter = true
// show about image content
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)
llCardImage.vg(is_shown)
btnCardImageShow.vg(! is_shown)
}
if(bShowOuter) 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("<br>")
sb.append(HTMLDecoder.encodeEntity(header)).append(": ")
if(url != null && url.isNotEmpty()) {
sb.append("<a href=\"").append(HTMLDecoder.encodeEntity(url)).append("\">")
}
sb.append(
HTMLDecoder.encodeEntity(
when {
caption != null && caption.isNotEmpty() -> caption
url != null && url.isNotEmpty() -> url
else -> "???"
}
)
)
if(url != null && url.isNotEmpty()) {
sb.append("</a>")
}
}
private fun makeReactionsView(status : TootStatus) {
if(! access_info.isMisskey) return
val reactionsCount = status.reactionCounts
val density = activity.density
val buttonHeight = ActMain.boostButtonSize
val marginBetween = (buttonHeight.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 box = FlexboxLayout(activity).apply {
flexWrap = FlexWrap.WRAP
justifyContent = JustifyContent.FLEX_START
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
).apply {
topMargin = (0.5f + density * 3f).toInt()
}
}
// +ボタン
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_round6dp
)
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)
}
}
2018-12-01 00:02:18 +01:00
b.setOnLongClickListener {
Action_Toot.reactionFromAnotherAccount(
activity,
access_info,
status_showing
)
true
}
2018-11-18 03:38:52 +01:00
2018-12-01 00:02:18 +01:00
setIconDrawableId(
2018-11-18 03:38:52 +01:00
activity,
b,
if(hasMyReaction) R.drawable.ic_remove else R.drawable.ic_add,
color = content_color,
alphaMultiplier = Styler.boost_alpha
2018-11-18 03:38:52 +01:00
)
box.addView(b)
}
if(reactionsCount != null) {
var lastButton : View? = null
val options = DecodeOptions(
activity,
access_info,
decodeEmoji = true,
enlargeEmoji = 1.5f
)
// 通常の絵文字はUnicodeを使う
fun addEmojiReaction(name : String, unicode : String, count : Int) {
val b = Button(activity).apply {
layoutParams = FlexboxLayout.LayoutParams(
FlexboxLayout.LayoutParams.WRAP_CONTENT,
buttonHeight
).apply {
endMargin = marginBetween
}
text = EmojiDecoder.decodeEmoji(options, "$unicode $count")
allCaps = false
tag = name
minWidthCompat = buttonHeight
background = ContextCompat.getDrawable(
this@ItemViewHolder.activity,
R.drawable.btn_bg_transparent_round6dp
)
// TODO 自分がリアクションしたやつは背景を変える
setTextColor(content_color)
setPadding(paddingH, paddingV, paddingH, paddingV)
setOnClickListener {
addReaction(status, it.tag as? String)
}
setOnLongClickListener {
Action_Toot.reactionFromAnotherAccount(
this@ItemViewHolder.activity,
access_info,
status_showing,
it.tag as? String
)
true
}
}
box.addView(b)
lastButton = b
}
fun addCustomEmojiReaction(name : String, customCode : String, count : Int) {
val b = Button(activity).apply {
layoutParams = FlexboxLayout.LayoutParams(
FlexboxLayout.LayoutParams.WRAP_CONTENT,
buttonHeight
).apply {
endMargin = marginBetween
}
minWidthCompat = buttonHeight
background = ContextCompat.getDrawable(
this@ItemViewHolder.activity,
R.drawable.btn_bg_transparent_round6dp
)
// TODO 自分がリアクションしたやつは背景を変える
setTextColor(content_color)
setPadding(paddingH, paddingV, paddingH, paddingV)
val emoji = status.custom_emojis?.get(customCode)
?: App1.custom_emoji_lister.getMap(access_info.host.ascii, true)
?.get(customCode)
val emojiUrl = emoji?.let {
2020-01-10 14:42:37 +01:00
if(Pref.bpDisableEmojiAnimation(this@ItemViewHolder.activity.pref)) {
it.static_url
} else {
it.url
}
2020-01-10 14:42:37 +01:00
}
val sb = SpannableStringBuilder("$name $count")
if(emojiUrl != null) {
sb.setSpan(
NetworkEmojiSpan(emojiUrl, scale = 1.5f),
0,
name.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
val invalidator =
NetworkEmojiInvalidator(this@ItemViewHolder.activity.handler, this)
invalidator.register(sb)
extra_invalidator_list.add(invalidator)
}
text = sb
allCaps = false
tag = name
setOnClickListener {
addReaction(status, it.tag as? String)
}
setOnLongClickListener {
Action_Toot.reactionFromAnotherAccount(
this@ItemViewHolder.activity,
access_info,
status_showing,
it.tag as? String
)
true
}
}
box.addView(b)
lastButton = b
}
for(entry in reactionsCount.entries) {
val key = entry.key
val count = entry.value
if(count <= 0) continue
// 組み込みのリアクション
val mr = MisskeyReaction.shortcodeMap[key]
if(mr != null) {
addEmojiReaction(mr.shortcode, mr.emojiUtf16, count)
continue
}
2020-01-10 14:42:37 +01:00
// カスタム絵文字のリアクション
val customCode = key.replace(":", "")
if(key != customCode) {
addCustomEmojiReaction(key, customCode, count)
continue
}
// Unicode絵文字のリアクション
addEmojiReaction(key, key, count)
}
lastButton
?.layoutParams
?.cast<ViewGroup.MarginLayoutParams>()
?.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()
.appendMisskeyReaction(activity, mr.emojiUtf16, " ")
.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().apply {
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().apply {
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
)
}
}
}
})
}
2019-04-14 05:41:29 +02:00
private fun showEnqueteItems(status : TootStatus, enquete : TootPolls) {
2019-03-06 10:49:02 +01:00
val items = enquete.items ?: return
val now = System.currentTimeMillis()
val canVote = when(enquete.pollType) {
2019-04-14 05:41:29 +02:00
TootPollsType.Mastodon -> when {
2019-03-06 10:49:02 +01:00
enquete.expired -> false
now >= enquete.expired_at -> false
enquete.ownVoted -> false
2019-03-06 10:49:02 +01:00
else -> true
}
2019-04-14 05:41:29 +02:00
TootPollsType.FriendsNico -> {
val remain = enquete.time_start + TootPolls.ENQUETE_EXPIRE - now
remain > 0L && ! enquete.ownVoted
2019-03-06 10:49:02 +01:00
}
TootPollsType.Misskey -> ! enquete.ownVoted
2019-03-06 10:49:02 +01:00
}
items.forEachIndexed { index, choice ->
makeEnqueteChoiceView(status, enquete, canVote, index, choice)
}
when(enquete.pollType) {
2019-04-14 05:41:29 +02:00
TootPollsType.Mastodon -> makeEnqueteFooterMastodon(status, enquete, canVote)
2019-03-06 10:49:02 +01:00
2019-04-14 05:41:29 +02:00
TootPollsType.FriendsNico -> makeEnqueteFooterFriendsNico(enquete)
2019-03-06 10:49:02 +01:00
2019-04-14 05:41:29 +02:00
TootPollsType.Misskey -> {
2019-03-06 10:49:02 +01:00
}
}
}
private fun makeEnqueteChoiceView(
2019-03-06 10:49:02 +01:00
status : TootStatus,
2019-04-14 05:41:29 +02:00
enquete : TootPolls,
2019-03-06 10:49:02 +01:00
canVote : Boolean,
i : Int,
2019-04-14 05:41:29 +02:00
item : TootPollsChoice
) {
2019-03-06 10:49:02 +01:00
val text = when(enquete.pollType) {
2019-04-14 05:41:29 +02:00
TootPollsType.Misskey -> {
2019-03-06 10:49:02 +01:00
val sb = SpannableStringBuilder()
.append(item.decoded_text)
if(enquete.ownVoted) {
2019-03-06 10:49:02 +01:00
sb.append(" / ")
sb.append(activity.getString(R.string.vote_count_text, item.votes))
if(item.isVoted) sb.append(' ').append(0x2713.toChar())
2019-03-06 10:49:02 +01:00
}
sb
}
2019-04-14 05:41:29 +02:00
TootPollsType.FriendsNico -> {
2019-03-06 10:49:02 +01:00
item.decoded_text
}
2019-04-14 05:41:29 +02:00
TootPollsType.Mastodon -> if(canVote) {
2019-03-06 10:49:02 +01:00
item.decoded_text
} else {
val sb = SpannableStringBuilder()
.append(item.decoded_text)
if(! canVote) {
val v = item.votes
2019-03-06 10:49:02 +01:00
sb.append(" / ")
sb.append(
when {
v == null ||
(column.isSearchColumn && column.access_info.isNA) ->
activity.getString(R.string.vote_count_unavailable)
else ->
activity.getString(R.string.vote_count_text, v)
2019-03-06 10:49:02 +01:00
}
)
if(item.isVoted) sb.append(' ').append(0x2713.toChar())
2019-03-06 10:49:02 +01:00
}
sb
}
2018-08-24 18:24:11 +02:00
}
2019-03-06 10:49:02 +01:00
// 投票ボタンの表示
val lp = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
2019-03-06 10:49:02 +01:00
).apply {
if(i == 0) topMargin = (0.5f + activity.density * 3f).toInt()
2018-08-24 18:24:11 +02:00
}
2019-03-06 10:49:02 +01:00
if(! canVote) {
2019-03-06 10:49:02 +01:00
val b = TextView(activity)
b.layoutParams = lp
2019-03-06 10:49:02 +01:00
b.text = text
val invalidator = NetworkEmojiInvalidator(activity.handler, b)
extra_invalidator_list.add(invalidator)
invalidator.register(text)
2019-03-06 10:49:02 +01:00
b.padding = (activity.density * 3f + 0.5f).toInt()
val ratio = when(enquete.pollType) {
TootPollsType.Mastodon -> {
val votesCount = enquete.votes_count ?: 0
val max = enquete.maxVotesCount ?: 0
if(max > 0 && votesCount > 0) {
(item.votes ?: 0).toFloat() / votesCount.toFloat()
} else {
2019-03-06 10:49:02 +01:00
null
}
}
else -> {
2019-03-06 10:49:02 +01:00
val ratios = enquete.ratios
if(ratios != null && i <= ratios.size) {
2019-03-06 10:49:02 +01:00
ratios[i]
} else {
2019-03-06 10:49:02 +01:00
null
}
}
}
if(ratio != null) {
2019-03-06 10:49:02 +01:00
b.backgroundDrawable = PollPlotDrawable(
color = (content_color and 0xFFFFFF) or 0x20000000,
ratio = ratio,
isRtl = b.layoutDirection == View.LAYOUT_DIRECTION_RTL,
startWidth = (activity.density * 2f + 0.5f).toInt()
)
}
2019-03-06 10:49:02 +01:00
llExtra.addView(b)
2019-03-06 10:49:02 +01:00
} else if(enquete.multiple) {
// 複数選択なのでチェックボックス
val b = CheckBox(activity)
b.layoutParams = lp
b.isAllCaps = false
b.text = text
val invalidator = NetworkEmojiInvalidator(activity.handler, b)
extra_invalidator_list.add(invalidator)
invalidator.register(text)
if(! canVote) {
b.isEnabled = false
} else {
b.isChecked = item.checked
b.setOnCheckedChangeListener { _, checked ->
item.checked = checked
}
}
llExtra.addView(b)
} else {
2019-03-06 10:49:02 +01:00
val b = Button(activity)
b.layoutParams = lp
b.isAllCaps = false
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(status, enquete, context, accessInfo, i)
}
}
2019-03-06 10:49:02 +01:00
llExtra.addView(b)
}
}
2019-04-14 05:41:29 +02:00
private fun makeEnqueteFooterFriendsNico(enquete : TootPolls) {
val density = activity.density
val height = (0.5f + 6 * density).toInt()
val view = EnqueteTimerView(activity)
view.layoutParams =
LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, height)
2019-04-14 05:41:29 +02:00
view.setParams(enquete.time_start, TootPolls.ENQUETE_EXPIRE)
llExtra.addView(view)
}
2019-03-06 10:49:02 +01:00
private fun makeEnqueteFooterMastodon(
status : TootStatus,
2019-04-14 05:41:29 +02:00
enquete : TootPolls,
2019-03-06 10:49:02 +01:00
canVote : Boolean
) {
val density = activity.density
if(canVote && enquete.multiple) {
// 複数選択の投票ボタン
val lp = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
).apply {
topMargin = (0.5f + density * 3f).toInt()
}
val b = Button(activity)
b.layoutParams = lp
b.isAllCaps = false
b.text = activity.getString(R.string.vote_button)
val accessInfo = this@ItemViewHolder.access_info
b.setOnClickListener { view ->
val context = view.context ?: return@setOnClickListener
sendMultiple(status, enquete, context, accessInfo)
}
llExtra.addView(b)
}
val tv = TextView(activity)
val lp = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
lp.topMargin = (0.5f + 3 * density).toInt()
tv.layoutParams = lp
val sb = StringBuilder()
val votes_count = enquete.votes_count ?: 0
when {
votes_count == 1 -> sb.append(activity.getString(R.string.vote_1))
votes_count > 1 -> sb.append(activity.getString(R.string.vote_2, votes_count))
}
when(val t = enquete.expired_at) {
Long.MAX_VALUE -> {
}
else -> {
if(sb.isNotEmpty()) sb.append(" ")
sb.append(
activity.getString(
R.string.vote_expire_at,
TootStatus.formatTime(activity, t, false)
)
)
}
}
tv.text = sb.toString()
llExtra.addView(tv)
}
private fun onClickEnqueteChoice(
2019-03-06 10:49:02 +01:00
status : TootStatus,
2019-04-14 05:41:29 +02:00
enquete : TootPolls,
context : Context,
accessInfo : SavedAccount,
idx : Int
) {
if(enquete.ownVoted) {
2018-08-24 18:24:11 +02:00
showToast(context, false, R.string.already_voted)
return
}
2019-03-06 10:49:02 +01:00
val now = System.currentTimeMillis()
when(enquete.pollType) {
2019-04-14 05:41:29 +02:00
TootPollsType.Misskey -> {
2019-03-06 10:49:02 +01:00
// Misskeyのアンケートには期限がない
}
2019-04-14 05:41:29 +02:00
TootPollsType.FriendsNico -> {
val remain = enquete.time_start + TootPolls.ENQUETE_EXPIRE - now
2019-03-06 10:49:02 +01:00
if(remain <= 0L) {
showToast(context, false, R.string.enquete_was_end)
return
}
}
2019-04-14 05:41:29 +02:00
TootPollsType.Mastodon -> {
2019-03-06 10:49:02 +01:00
if(enquete.expired || now >= enquete.expired_at) {
showToast(context, false, R.string.enquete_was_end)
return
}
2018-08-24 18:24:11 +02:00
}
}
TootTaskRunner(context).run(accessInfo, object : TootTask {
2019-03-06 10:49:02 +01:00
override fun background(client : TootApiClient) = when(enquete.pollType) {
2019-04-14 05:41:29 +02:00
TootPollsType.Misskey -> client.request(
2019-03-06 10:49:02 +01:00
"/api/notes/polls/vote",
accessInfo.putMisskeyApiToken().apply {
put("noteId", enquete.status_id.toString())
put("choice", idx)
}.toPostRequestBuilder()
2019-03-06 10:49:02 +01:00
)
2019-04-14 05:41:29 +02:00
TootPollsType.Mastodon -> client.request(
2019-03-06 10:49:02 +01:00
"/api/v1/polls/${enquete.pollId}/votes",
jsonObject {
put("choices", jsonArray { add(idx) })
}.toPostRequestBuilder()
2019-03-06 10:49:02 +01:00
)
2019-04-14 05:41:29 +02:00
TootPollsType.FriendsNico -> client.request(
2019-03-06 10:49:02 +01:00
"/api/v1/votes/${enquete.status_id}",
jsonObject {
put("item_index", idx.toString())
}.toPostRequestBuilder()
2019-03-06 10:49:02 +01:00
)
}
override fun handleResult(result : TootApiResult?) {
result ?: return // cancelled.
val data = result.jsonObject
if(data != null) {
2019-03-06 10:49:02 +01:00
when(enquete.pollType) {
2019-04-14 05:41:29 +02:00
TootPollsType.Misskey -> if(enquete.increaseVote(activity, idx, true)) {
showToast(context, false, R.string.enquete_voted)
// 1個だけ開閉するのではなく、例えば通知TLにある複数の要素をまとめて開閉するなどある
list_adapter.notifyChange(reason = "onClickEnqueteChoice", reset = true)
}
2019-04-14 05:41:29 +02:00
TootPollsType.Mastodon -> {
val newPoll = TootPolls.parse(
2019-03-06 10:49:02 +01:00
TootParser(activity, accessInfo),
status,
status.media_attachments,
data,
2019-04-14 05:41:29 +02:00
TootPollsType.Mastodon
2019-03-06 10:49:02 +01:00
)
if(newPoll != null) {
status.enquete = newPoll
// 1個だけ開閉するのではなく、例えば通知TLにある複数の要素をまとめて開閉するなどある
list_adapter.notifyChange(
reason = "onClickEnqueteChoice",
reset = true
)
} else if(result.error != null) {
showToast(context, true, "response parse error")
}
2018-08-24 18:24:11 +02:00
}
2019-04-14 05:41:29 +02:00
TootPollsType.FriendsNico -> {
2020-01-08 04:23:45 +01:00
val message = data.string("message") ?: "?"
2019-03-06 10:49:02 +01:00
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)
}
}
})
}
2019-03-06 10:49:02 +01:00
private fun sendMultiple(
status : TootStatus,
2019-04-14 05:41:29 +02:00
enquete : TootPolls,
2019-03-06 10:49:02 +01:00
context : Context,
accessInfo : SavedAccount
) {
val now = System.currentTimeMillis()
if(now >= enquete.expired_at) {
showToast(context, false, R.string.enquete_was_end)
return
}
if(enquete.items?.find { it.checked } == null) {
showToast(context, false, R.string.polls_choice_not_selected)
return
}
2019-03-06 10:49:02 +01:00
TootTaskRunner(context).run(accessInfo, object : TootTask {
2019-04-14 05:41:29 +02:00
var newPoll : TootPolls? = null
2019-03-06 10:49:02 +01:00
override fun background(client : TootApiClient) : TootApiResult? {
return client.request(
"/api/v1/polls/${enquete.pollId}/votes",
jsonObject {
put("choices", jsonArray {
enquete.items.forEachIndexed { index, choice ->
if(choice.checked) add(index)
2019-03-06 10:49:02 +01:00
}
})
}.toPostRequestBuilder()
2019-03-06 10:49:02 +01:00
)?.also { result ->
val data = result.jsonObject
if(data != null) {
2019-04-14 05:41:29 +02:00
newPoll = TootPolls.parse(
2019-03-06 10:49:02 +01:00
TootParser(activity, accessInfo),
status,
status.media_attachments,
data,
2019-04-14 05:41:29 +02:00
TootPollsType.Mastodon
2019-03-06 10:49:02 +01:00
)
if(newPoll == null) result.setError("response parse error")
}
}
}
override fun handleResult(result : TootApiResult?) {
result ?: return // cancelled.
val newPoll = this.newPoll
if(newPoll != null) {
status.enquete = newPoll
// 1個だけ開閉するのではなく、例えば通知TLにある複数の要素をまとめて開閉するなどある
list_adapter.notifyChange(reason = "onClickEnqueteChoice", reset = true)
} else if(result.error != null) {
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))
}
internal fun getAccount() = status_account ?: boost_account ?: follow_account
/////////////////////////////////////////////////////////////////////
private fun inflate(activity : ActMain) = with(activity.UI {}) {
val b = Benchmark(log, "Item-Inflate", 40L)
val rv = verticalLayout {
// トップレベルのViewGroupのlparamsはイニシャライザ内部に置くしかないみたい
2019-03-06 10:49:02 +01:00
layoutParams =
androidx.recyclerview.widget.RecyclerView.LayoutParams(matchParent, wrapContent)
.apply {
marginStart = dip(8)
marginEnd = dip(8)
topMargin = dip(2f)
bottomMargin = dip(1f)
}
2018-12-01 00:02:18 +01:00
setPaddingRelative(dip(4), dip(1f), dip(4), dip(2f))
descendantFocusability = ViewGroup.FOCUS_BLOCK_DESCENDANTS
llBoosted = linearLayout {
lparams(matchParent, wrapContent) {
bottomMargin = dip(6)
}
backgroundResource = R.drawable.btn_bg_transparent_round6dp
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_round6dp)
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)
2019-08-30 06:02:08 +02:00
tvLastStatusAt = myTextView {
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_round6dp
)
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_round6dp
)
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_round6dp
)
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 {
backgroundDrawable =
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)
}
2020-02-12 07:58:06 +01:00
val thumbnailHeight = activity.app_state.media_thumb_height
val verticalArrangeThumbnails =
Pref.bpVerticalArrangeThumbnails(activity.pref)
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_round6dp
)
contentDescription = context.getString(R.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 = blurhashView {
errorColor = getAttributeColor(
context,
R.attr.colorShowMediaBackground
)
gravity = Gravity.CENTER
textColor = getAttributeColor(
context,
R.attr.colorShowMediaText
)
minHeightCompat = dip(48)
2019-08-23 01:49:20 +02:00
}.lparams(matchParent, thumbnailHeight)
2019-05-05 06:41:13 +02:00
}
} 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_round6dp
)
contentDescription = context.getString(R.string.hide)
imageResource = R.drawable.ic_close
}.lparams(dip(32), matchParent) {
startMargin = dip(8)
}
}
2019-05-05 06:41:13 +02:00
btnShowMedia = blurhashView {
2019-05-05 06:41:13 +02:00
errorColor = getAttributeColor(
context,
R.attr.colorShowMediaBackground
)
gravity = Gravity.CENTER
2019-05-05 06:41:13 +02:00
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) {
}
flCardImage = frameLayout {
lparams(matchParent, activity.app_state.media_thumb_height) {
topMargin = dip(3)
}
llCardImage = linearLayout {
lparams(matchParent, matchParent)
ivCardImage = myNetworkImageView {
contentDescription =
context.getString(R.string.thumbnail)
scaleType = if(Pref.bpDontCropMediaThumb(App1.pref))
ImageView.ScaleType.FIT_CENTER
else
ImageView.ScaleType.CENTER_CROP
}.lparams(0, matchParent) {
weight = 1f
}
btnCardImageHide = imageButton {
background = ContextCompat.getDrawable(
context,
R.drawable.btn_bg_transparent_round6dp
)
contentDescription = context.getString(R.string.hide)
imageResource = R.drawable.ic_close
}.lparams(dip(32), matchParent) {
startMargin = dip(4)
}
}
btnCardImageShow = button {
backgroundColor = getAttributeColor(
context,
R.attr.colorShowMediaBackground
)
gravity = Gravity.CENTER
textColor = getAttributeColor(
context,
R.attr.colorShowMediaText
)
}.lparams(matchParent, matchParent)
}
}
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_round6dp)
allCaps = false
}.lparams(matchParent, wrapContent)
}
llTrendTag = linearLayout {
lparams(matchParent, wrapContent)
gravity = Gravity.CENTER_VERTICAL
background =
ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent_round6dp)
verticalLayout {
lparams(0, wrapContent) {
weight = 1f
}
tvTrendTagName = textView {
}.lparams(matchParent, wrapContent)
2018-11-18 03:38:52 +01:00
tvTrendTagDesc = textView {
textSize = 12f // SP
}.lparams(matchParent, wrapContent)
}
tvTrendTagCount = textView {
}.lparams(wrapContent, wrapContent) {
startMargin = dip(6)
endMargin = dip(6)
}
cvTagHistory = trendTagHistoryView {
}.lparams(dip(64), dip(32))
}
llList = linearLayout {
lparams(matchParent, wrapContent)
gravity = Gravity.CENTER_VERTICAL
isBaselineAligned = false
minimumHeight = dip(40)
btnListTL = button {
background =
ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent_round6dp)
allCaps = false
}.lparams(0, wrapContent) {
weight = 1f
}
btnListMore = imageButton {
background =
ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent_round6dp)
imageResource = R.drawable.ic_more
contentDescription = context.getString(R.string.more)
}.lparams(dip(40), matchParent) {
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_round6dp)
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_round6dp)
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
}
}