package jp.juggler.subwaytooter.itemviewholder import android.annotation.SuppressLint import android.content.res.ColorStateList import android.graphics.Typeface import android.os.SystemClock import android.text.Spannable import android.text.SpannableString import android.text.SpannableStringBuilder import android.text.style.StyleSpan import android.view.View import android.widget.Button import android.widget.TextView import androidx.annotation.StringRes import jp.juggler.subwaytooter.* import jp.juggler.subwaytooter.actmain.closePopup import jp.juggler.subwaytooter.api.TootParser import jp.juggler.subwaytooter.api.entity.* import jp.juggler.subwaytooter.column.Column import jp.juggler.subwaytooter.column.ColumnType import jp.juggler.subwaytooter.column.getAcctColor import jp.juggler.subwaytooter.column.getContentColor import jp.juggler.subwaytooter.columnviewholder.ItemListAdapter import jp.juggler.subwaytooter.drawable.PreviewCardBorder import jp.juggler.subwaytooter.pref.PrefB import jp.juggler.subwaytooter.pref.PrefI import jp.juggler.subwaytooter.span.MyClickableSpan import jp.juggler.subwaytooter.util.emojiSizeMode import jp.juggler.subwaytooter.table.* import jp.juggler.subwaytooter.util.DecodeOptions import jp.juggler.subwaytooter.view.MyNetworkImageView import jp.juggler.util.* import jp.juggler.util.data.* import jp.juggler.util.log.* import jp.juggler.util.ui.* import org.jetbrains.anko.backgroundColor import org.jetbrains.anko.textColor import kotlin.math.max private val log = LogCategory("ItemViewHolderShow") @SuppressLint("ClickableViewAccessibility") fun ItemViewHolder.bind( listAdapter: ItemListAdapter, column: Column, bSimpleList: Boolean, item: TimelineItem, ) { val b = Benchmark(ItemViewHolder.log, "Item-bind", 40L) this.listAdapter = listAdapter this.column = column this.bSimpleList = bSimpleList this.accessInfo = column.accessInfo val fontBold = ActMain.timelineFontBold val fontNormal = ActMain.timelineFont viewRoot.scan { v -> try { when (v) { // ボタンは太字なので触らない is Button, is CountImageButton -> Unit is TextView -> v.typeface = when { v === tvName || v === tvFollowerName || v === tvBoosted || v === tvReply || v === tvTrendTagCount || v === tvTrendTagName || v === tvConversationIconsMore || v === tvConversationParticipants || v === tvFilterPhrase -> fontBold else -> fontNormal } } } catch (ex: Throwable) { log.e(ex, "can't change font.") } } if (bSimpleList) { viewRoot.setOnTouchListener { _, ev -> // ポップアップを閉じた時にクリックでリストを触ったことになってしまう不具合の回避 val now = SystemClock.elapsedRealtime() // ポップアップを閉じた直後はタッチダウンを無視する if (now - StatusButtonsPopup.lastPopupClose >= 30L) { false } else { val action = ev.action ItemViewHolder.log.d("onTouchEvent action=$action") true } } viewRoot.setOnClickListener { viewClicked -> activity.closePopup() statusShowing?.let { status -> val popup = StatusButtonsPopup(activity, column, true, this@bind) activity.popupStatusButtons = popup popup.show( listAdapter.columnVh.listView, viewClicked, status, item as? TootNotification ) } } llButtonBar.visibility = View.GONE this.buttonsForStatus = null } else { viewRoot.isClickable = false llButtonBar.visibility = View.VISIBLE this.buttonsForStatus = StatusButtons( activity, column, false, statusButtonsViewHolder, this ) } this.statusShowing = null this.statusReply = null this.statusAccount = null this.boostAccount = null this.followAccount = null this.boostTime = 0L this.viewRoot.setBackgroundColor(0) this.boostedAction = defaultBoostedAction llOpenSticker.visibility = View.GONE llBoosted.visibility = View.GONE llReply.visibility = View.GONE llFollow.visibility = View.GONE llStatus.visibility = View.GONE llSearchTag.visibility = View.GONE btnGapHead.visibility = View.GONE btnGapTail.visibility = View.GONE llList.visibility = View.GONE llFollowRequest.visibility = View.GONE tvMessageHolder.visibility = View.GONE llTrendTag.visibility = View.GONE llFilter.visibility = View.GONE tvMediaDescriptions.forEach { it.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() this.colorTextContent = c this.contentColorCsl = ColorStateList.valueOf(c) tvBoosted.setTextColor(c) tvReply.setTextColor(c) tvFollowerName.setTextColor(c) tvName.setTextColor(c) tvMentions.setTextColor(c) tvContentWarning.setTextColor(c) tvContent.setTextColor(c) //NSFWは文字色固定 btnShowMedia.setTextColor( c ); tvApplication.setTextColor(c) tvMessageHolder.setTextColor(c) tvTrendTagName.setTextColor(c) tvTrendTagCount.setTextColor(c) cvTagHistory.setColor(c) tvFilterPhrase.setTextColor(c) tvMediaDescriptions.forEach { it.setTextColor(c) } tvCardText.setTextColor(c) tvConversationIconsMore.setTextColor(c) tvConversationParticipants.setTextColor(c) (llCardOuter.background as? PreviewCardBorder)?.let { val rgb = c and 0xffffff val alpha = max(1, c ushr (24 + 1)) // 本来の値の半分にする it.color = rgb or (alpha shl 24) } c = column.getAcctColor() this.acctColor = c tvBoostedTime.setTextColor(c) tvTime.setTextColor(c) tvTrendTagDesc.setTextColor(c) tvFilterDetail.setTextColor(c) tvFilterPhrase.setTextColor(c) // 以下のビューの文字色はsetAcct() で設定される // tvBoostedAcct.setTextColor(c) // tvFollowerAcct.setTextColor(c) // tvAcct.setTextColor(c) this.item = item when (item) { is TootStatus -> { val reblog = item.reblog when { reblog == null -> showStatusOrReply(item) item.isQuoteToot -> { // 引用Renote val colorBg = PrefI.ipEventBgColorBoost.value showReply(item.account, reblog, R.drawable.ic_quote, R.string.quote_to) showStatus(item, colorBg) } else -> { // 引用なしブースト val colorBg = PrefI.ipEventBgColorBoost.value showBoost( item.accountRef, item.time_created_at, R.drawable.ic_repeat, R.string.display_name_boosted_by, boostStatus = item ) showStatusOrReply(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 MisskeyAntenna -> showAntenna(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) } b.report() } fun ItemViewHolder.removeExtraView() { llExtra.scan { v -> if (v is MyNetworkImageView) { v.cancelLoading() } } llExtra.removeAllViews() for (invalidator in extraInvalidatorList) { invalidator.register(null) } extraInvalidatorList.clear() } fun ItemViewHolder.showAccount(whoRef: TootAccountRef) { followAccount = whoRef val who = whoRef.get() llFollow.visibility = View.VISIBLE ivFollow.setImageUrl( calcIconRound(ivFollow.layoutParams), accessInfo.supplyBaseUrl(who.avatar_static), accessInfo.supplyBaseUrl(who.avatar) ) followInvalidator.text = whoRef.decoded_display_name setAcct(tvFollowerAcct, accessInfo, who) who.setAccountExtra( accessInfo, lastActiveInvalidator, suggestionSource = if (column.type == ColumnType.FOLLOW_SUGGESTION) { SuggestionSource.get(accessInfo.db_id, who.acct) } else { null } ) val relation = daoUserRelation.load(accessInfo.db_id, who.id) setFollowIcon( activity, btnFollow, ivFollowedBy, relation, who, colorTextContent, alphaMultiplier = stylerBoostAlpha ) if (column.type == ColumnType.FOLLOW_REQUESTS) { llFollowRequest.visibility = View.VISIBLE btnFollowRequestAccept.imageTintList = contentColorCsl btnFollowRequestDeny.imageTintList = contentColorCsl } } fun ItemViewHolder.showAntenna(a: MisskeyAntenna) { llList.visibility = View.VISIBLE btnListTL.text = a.name btnListTL.textColor = colorTextContent btnListMore.imageTintList = contentColorCsl } fun ItemViewHolder.showBoost( whoRef: TootAccountRef, time: Long, iconId: Int, @StringRes stringId: Int, reaction: TootReaction? = null, boostStatus: TootStatus? = null, reblogVisibility: TootVisibility? = null, ) { boostAccount = whoRef val who = whoRef.get() setIconDrawableId( activity, ivBoosted, iconId, color = colorTextContent, alphaMultiplier = stylerBoostAlpha ) ivBoostAvatar.let { v -> v.setImageUrl( calcIconRound(v.layoutParams), accessInfo.supplyBaseUrl(who.avatar_static), accessInfo.supplyBaseUrl(who.avatar) ) } boostTime = time llBoosted.visibility = View.VISIBLE showStatusTime( activity, tvBoostedTime, who, time = time, status = boostStatus, reblogVisibility = reblogVisibility ) setAcct(tvBoostedAcct, accessInfo, who) // フォローの場合 decoded_display_name が2箇所で表示に使われるのを避ける必要がある if (reaction != null) { val options = DecodeOptions( activity, accessInfo, decodeEmoji = true, enlargeEmoji = DecodeOptions.emojiScaleReaction, enlargeCustomEmoji = DecodeOptions.emojiScaleReaction, emojiSizeMode = accessInfo.emojiSizeMode(), ) val ssb = reaction.toSpannableStringBuilder(options, boostStatus) ssb.append(" ") ssb.append( who.decodeDisplayNameCached(activity) .intoStringResource(activity, stringId) ) boostInvalidator.text = ssb } else { boostInvalidator.text = who.decodeDisplayNameCached(activity) .intoStringResource(activity, stringId) } } fun ItemViewHolder.showMessageHolder(item: TootMessageHolder) { tvMessageHolder.visibility = View.VISIBLE tvMessageHolder.text = item.text tvMessageHolder.gravity = item.gravity } fun ItemViewHolder.showList(list: TootList) { llList.visibility = View.VISIBLE btnListTL.text = list.title btnListTL.textColor = colorTextContent btnListMore.imageTintList = contentColorCsl } fun ItemViewHolder.showDomainBlock(domainBlock: TootDomainBlock) { llSearchTag.visibility = View.VISIBLE btnSearchTag.text = domainBlock.domain.pretty } fun ItemViewHolder.showFilter(filter: TootFilter) { llFilter.visibility = View.VISIBLE tvFilterPhrase.text = filter.displayString tvFilterDetail.text = StringBuffer().apply { val contextNames = filter.contextNames.joinToString("/") { activity.getString(it) } append(activity.getString(R.string.filter_context)) append(": ") append(contextNames) val action = when (filter.hide) { true -> activity.getString(R.string.filter_action_hide) else -> activity.getString(R.string.filter_action_warn) } append('\n') append(activity.getString(R.string.filter_action)) append(": ") append(action) if (filter.time_expires_at > 0L) { append('\n') append(activity.getString(R.string.filter_expires_at)) append(": ") append(TootStatus.formatTime(activity, filter.time_expires_at, false)) } }.toString() } fun ItemViewHolder.showSearchTag(tag: TootTag) { if (tag.history?.isNotEmpty() == true) { llTrendTag.visibility = View.VISIBLE tvTrendTagCount.text = "${tag.countDaily}(${tag.countWeekly})" cvTagHistory.setHistory(tag.history) when (tag.type) { TootTag.TagType.Link -> { tvTrendTagName.text = tag.url?.ellipsizeDot3(256) tvTrendTagDesc.text = tag.name + "\n" + tag.description } TootTag.TagType.Tag -> { tvTrendTagName.text = "#${tag.name.ellipsizeDot3(256)}" tvTrendTagDesc.text = listOf( when (tag.following) { true -> activity.getString(R.string.following) else -> "" }, activity.getString(R.string.people_talking, tag.accountDaily, tag.accountWeekly) ).filter { it.isNotEmpty() }.joinToString(" ") } } } else { llSearchTag.visibility = View.VISIBLE btnSearchTag.text = "#" + tag.name } } fun ItemViewHolder.showGap() { llSearchTag.visibility = View.VISIBLE btnSearchTag.text = activity.getString(R.string.read_gap) btnGapHead.vg(column.type.gapDirection(column, true)) ?.imageTintList = contentColorCsl btnGapTail.vg(column.type.gapDirection(column, false)) ?.imageTintList = contentColorCsl val c = PrefI.ipEventBgColorGap.value if (c != 0) this.viewRoot.backgroundColor = c } fun ItemViewHolder.showSearchGap(item: TootSearchGap) { llSearchTag.visibility = View.VISIBLE 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 } ) } fun ItemViewHolder.showReply( // 返信した人 replyer: TootAccount?, // 返信された人 target: TootAccount?, iconId: Int, text: Spannable, ) { llReply.visibility = View.VISIBLE setIconDrawableId( activity, ivReply, iconId, color = colorTextContent, alphaMultiplier = stylerBoostAlpha ) ivReplyAvatar.vg(target != null && target.avatar != replyer?.avatar)?.let { v -> v.setImageUrl( calcIconRound(v.layoutParams), accessInfo.supplyBaseUrl(target!!.avatar_static), accessInfo.supplyBaseUrl(target.avatar) ) } replyInvalidator.text = text } fun ItemViewHolder.showReply(replyer: TootAccount?, reply: TootStatus, iconId: Int, stringId: Int) { statusReply = reply showReply( replyer = replyer, target = reply.accountRef.get(), iconId, reply.accountRef.decoded_display_name.intoStringResource(activity, stringId) ) } fun ItemViewHolder.showReply(replyer: TootAccount?, reply: TootStatus, accountId: EntityId) { val name = if (accountId == reply.account.id) { // 自己レスなら daoAcctColor.getNicknameWithColor(accessInfo, reply.account) } else { val m = reply.mentions?.find { it.id == accountId } if (m != null) { daoAcctColor.getNicknameWithColor(accessInfo.getFullAcct(m.acct)) } else { SpannableString("ID($accountId)") } } val text = name.intoStringResource(activity, R.string.reply_to) showReply(replyer = replyer, target = null, R.drawable.ic_reply, text) // tootsearchはreplyオブジェクトがなくin_reply_toだけが提供される場合があるが // tootsearchではどのタンスから読んだか分からないのでin_reply_toのIDも信用できない } fun ItemViewHolder.showStatusTime( activity: ActMain, tv: TextView, @Suppress("UNUSED_PARAMETER") who: TootAccount, status: TootStatus? = null, time: Long? = null, reblogVisibility: TootVisibility? = 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") } if (status.account.suspended) { if (sb.isNotEmpty()) sb.append('\u200B') sb.appendColorShadeIcon(activity, R.drawable.ic_delete, "suspended") } // mobileマーク if (status.viaMobile) { if (sb.isNotEmpty()) sb.append('\u200B') sb.appendColorShadeIcon(activity, R.drawable.ic_mobile, "mobile") } // ブックマーク済み if (status.bookmarked) { if (sb.isNotEmpty()) sb.append('\u200B') sb.appendColorShadeIcon(activity, R.drawable.ic_bookmark_added, "bookmarked") } // NSFWマーク if (status.hasMedia() && status.sensitive) { if (sb.isNotEmpty()) sb.append('\u200B') sb.appendColorShadeIcon(activity, R.drawable.ic_eye_off, "NSFW") } // visibility val visIconId = status.visibility.getVisibilityIconId(accessInfo.isMisskey) if (R.drawable.ic_public != visIconId) { if (sb.isNotEmpty()) sb.append('\u200B') sb.appendColorShadeIcon( activity, visIconId, status.visibility.getVisibilityString(accessInfo.isMisskey) ) } // 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 (status.isPromoted) { if (sb.isNotEmpty()) sb.append(' ') sb.append(activity.getString(R.string.promoted)) } if (status.isFeatured) { if (sb.isNotEmpty()) sb.append(' ') sb.append(activity.getString(R.string.featured)) } if (status.time_edited_at > 0L) { if (sb.isNotEmpty()) sb.append(' ') val start = sb.length sb.append(activity.getString(R.string.edited)) val end = sb.length sb.setSpan(StyleSpan(Typeface.BOLD), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) } } else { reblogVisibility?.takeIf { it != TootVisibility.Unknown }?.let { visibility -> val visIconId = visibility.getVisibilityIconId(accessInfo.isMisskey) if (R.drawable.ic_public != visIconId) { if (sb.isNotEmpty()) sb.append('\u200B') sb.appendColorShadeIcon( activity, visIconId, visibility.getVisibilityString(accessInfo.isMisskey) ) } } } if (sb.isNotEmpty()) sb.append(' ') sb.append( (time ?: status?.time_created_at)?.let { TootStatus.formatTime(activity, it, column.canRelativeTime) } ?: "?" ) tv.text = sb } fun ItemViewHolder.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 = item.visibility.getVisibilityIconId(accessInfo.isMisskey) if (R.drawable.ic_public != visIconId) { if (sb.isNotEmpty()) sb.append('\u200B') sb.appendColorShadeIcon( activity, visIconId, item.visibility.getVisibilityString(accessInfo.isMisskey) ) } if (sb.isNotEmpty()) sb.append(' ') sb.append(TootStatus.formatTime(activity, item.timeScheduledAt, column.canRelativeTime)) tv.text = sb } val Column.canRelativeTime get() = when (type) { ColumnType.CONVERSATION, ColumnType.CONVERSATION_WITH_REFERENCE, ColumnType.STATUS_HISTORY, -> false else -> true } // 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) // } // } fun ItemViewHolder.showScheduled(item: TootScheduled) { try { llStatus.visibility = View.VISIBLE this.viewRoot.setBackgroundColor(0) showStatusTimeScheduled(activity, tvTime, item) val who = column.whoAccount!!.get() val whoRef = TootAccountRef.tootAccountRef(TootParser(activity, accessInfo), who) this.statusAccount = whoRef setAcct(tvAcct, accessInfo, who) nameInvalidator.text = whoRef.decoded_display_name ivAvatar.setImageUrl( calcIconRound(ivAvatar.layoutParams), accessInfo.supplyBaseUrl(who.avatar_static), accessInfo.supplyBaseUrl(who.avatar) ) val content = SpannableString(item.text ?: "") tvMentions.visibility = View.GONE contentInvalidator.text = content tvContent.minLines = -1 val decodedSpoilerText = SpannableString(item.spoilerText ?: "") when { decodedSpoilerText.isNotEmpty() -> { // 元データに含まれるContent Warning を使う llContentWarning.visibility = View.VISIBLE spoilerInvalidator.text = decodedSpoilerText val cwShown = daoContentWarning.isShown(item.uri, accessInfo.expandCw) setContentVisibility(cwShown) } else -> { // CWしない llContentWarning.visibility = View.GONE llContents.visibility = View.VISIBLE } } val mediaAttachments = item.mediaAttachments if (mediaAttachments?.isEmpty() != false) { flMedia.visibility = View.GONE llMedia.visibility = View.GONE btnShowMedia.visibility = View.GONE } else { flMedia.visibility = View.VISIBLE // hide sensitive media val defaultShown = when { column.hideMediaDefault -> false accessInfo.dontHideNsfw -> true else -> !item.sensitive } val isShown = daoMediaShown.isShown(item.uri, defaultShown) btnShowMedia.visibility = if (!isShown) View.VISIBLE else View.GONE llMedia.visibility = if (!isShown) View.GONE else View.VISIBLE repeat(ItemViewHolder.MEDIA_VIEW_COUNT) { idx -> setMedia(mediaAttachments, idx) } setIconDrawableId( activity, btnHideMedia, R.drawable.ic_close, color = colorTextContent, alphaMultiplier = stylerBoostAlpha ) } buttonsForStatus?.hide() tvApplication.visibility = View.GONE } catch (ex: Throwable) { ItemViewHolder.log.w(ex, "showScheduled failed") } llSearchTag.visibility = View.VISIBLE btnSearchTag.text = activity.getString(R.string.scheduled_status) + " " + TootStatus.formatTime(activity, item.timeScheduledAt, true) } fun ItemViewHolder.showConversationIcons(cs: TootConversationSummary) { val lastAccountId = cs.last_status.account.id val accountsOther = cs.accounts.filter { it.get().id != lastAccountId } if (accountsOther.isNotEmpty()) { llConversationIcons.visibility = View.VISIBLE val size = accountsOther.size tvConversationParticipants.text = if (size <= 1) { activity.getString(R.string.conversation_to) } else { activity.getString(R.string.participants) } fun showIcon(iv: MyNetworkImageView, idx: Int) { val bShown = idx < size iv.visibility = if (bShown) View.VISIBLE else View.GONE if (!bShown) return val who = accountsOther[idx].get() iv.setImageUrl( calcIconRound(iv.layoutParams), accessInfo.supplyBaseUrl(who.avatar_static), accessInfo.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) } } fun ItemViewHolder.setAcct(tv: TextView, accessInfo: SavedAccount, who: TootAccount) { val ac = daoAcctColor.load(accessInfo, who) tv.text = when { daoAcctColor.hasNickname(ac) -> ac.nickname PrefB.bpShortAcctLocalUser.value -> "@${who.acct.pretty}" else -> "@${ac.nickname}" } tv.textColor = ac.colorFg.notZero() ?: this.acctColor tv.setBackgroundColor(ac.colorBg) // may 0 tv.setPaddingRelative(activity.acctPadLr, 0, activity.acctPadLr, 0) }