2831 lines
84 KiB
Kotlin
2831 lines
84 KiB
Kotlin
package jp.juggler.subwaytooter
|
|
|
|
import android.annotation.SuppressLint
|
|
import android.content.Context
|
|
import android.content.res.ColorStateList
|
|
import android.graphics.Bitmap
|
|
import android.graphics.Color
|
|
import android.graphics.Paint
|
|
import android.graphics.Path
|
|
import android.os.SystemClock
|
|
import android.text.InputType
|
|
import android.text.Spannable
|
|
import android.text.SpannableStringBuilder
|
|
import android.text.TextUtils
|
|
import android.view.*
|
|
import android.view.animation.AlphaAnimation
|
|
import android.view.animation.Animation
|
|
import android.view.inputmethod.EditorInfo
|
|
import android.widget.*
|
|
import androidx.core.content.ContextCompat
|
|
import androidx.recyclerview.widget.LinearLayoutManager
|
|
import androidx.recyclerview.widget.RecyclerView
|
|
import com.google.android.flexbox.FlexWrap
|
|
import com.google.android.flexbox.FlexboxLayout
|
|
import com.google.android.flexbox.JustifyContent
|
|
import com.omadahealth.github.swipyrefreshlayout.library.SwipyRefreshLayout
|
|
import com.omadahealth.github.swipyrefreshlayout.library.SwipyRefreshLayoutDirection
|
|
import jp.juggler.subwaytooter.action.Action_List
|
|
import jp.juggler.subwaytooter.action.Action_Notification
|
|
import jp.juggler.subwaytooter.api.TootApiClient
|
|
import jp.juggler.subwaytooter.api.TootApiResult
|
|
import jp.juggler.subwaytooter.api.TootTask
|
|
import jp.juggler.subwaytooter.api.TootTaskRunner
|
|
import jp.juggler.subwaytooter.api.entity.TootAnnouncement
|
|
import jp.juggler.subwaytooter.api.entity.TootStatus
|
|
import jp.juggler.subwaytooter.dialog.EmojiPicker
|
|
import jp.juggler.subwaytooter.span.NetworkEmojiSpan
|
|
import jp.juggler.subwaytooter.table.AcctColor
|
|
import jp.juggler.subwaytooter.util.*
|
|
import jp.juggler.subwaytooter.view.ListDivider
|
|
import jp.juggler.subwaytooter.view.MyLinkMovementMethod
|
|
import jp.juggler.subwaytooter.view.MyTextView
|
|
import jp.juggler.subwaytooter.view.OutsideDrawerLayout
|
|
import jp.juggler.util.*
|
|
import kotlinx.coroutines.*
|
|
import org.jetbrains.anko.*
|
|
import org.jetbrains.anko.custom.customView
|
|
import java.io.Closeable
|
|
import java.lang.Runnable
|
|
import java.lang.reflect.Field
|
|
import java.util.regex.Pattern
|
|
|
|
@SuppressLint("ClickableViewAccessibility")
|
|
class ColumnViewHolder(
|
|
val activity : ActMain,
|
|
parent : ViewGroup
|
|
) : View.OnClickListener,
|
|
SwipyRefreshLayout.OnRefreshListener,
|
|
CompoundButton.OnCheckedChangeListener, View.OnLongClickListener {
|
|
|
|
companion object {
|
|
private val log = LogCategory("ColumnViewHolder")
|
|
|
|
val fieldRecycler : Field by lazy {
|
|
val field = RecyclerView::class.java.getDeclaredField("mRecycler")
|
|
field.isAccessible = true
|
|
field
|
|
}
|
|
|
|
val fieldState : Field by lazy {
|
|
val field = RecyclerView::class.java.getDeclaredField("mState")
|
|
field.isAccessible = true
|
|
field
|
|
}
|
|
|
|
val heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
|
|
|
|
// var lastRefreshError : String = ""
|
|
// var lastRefreshErrorShown : Long = 0L
|
|
}
|
|
|
|
var column : Column? = null
|
|
private var status_adapter : ItemListAdapter? = null
|
|
private var page_idx : Int = 0
|
|
|
|
private lateinit var tvLoading : TextView
|
|
lateinit var listView : RecyclerView
|
|
lateinit var refreshLayout : SwipyRefreshLayout
|
|
|
|
lateinit var listLayoutManager : LinearLayoutManager
|
|
|
|
private lateinit var llColumnHeader : View
|
|
private lateinit var tvColumnIndex : TextView
|
|
private lateinit var tvColumnStatus : TextView
|
|
private lateinit var tvColumnContext : TextView
|
|
private lateinit var ivColumnIcon : ImageView
|
|
private lateinit var tvColumnName : TextView
|
|
|
|
private lateinit var llColumnSetting : View
|
|
private lateinit var llColumnSettingInside : LinearLayout
|
|
|
|
private lateinit var btnSearch : ImageButton
|
|
private lateinit var btnSearchClear : ImageButton
|
|
private lateinit var etSearch : EditText
|
|
private lateinit var cbResolve : CheckBox
|
|
private lateinit var etRegexFilter : EditText
|
|
private lateinit var tvRegexFilterError : TextView
|
|
|
|
private lateinit var btnAnnouncementsBadge : ImageView
|
|
private lateinit var btnAnnouncements : ImageButton
|
|
private lateinit var btnAnnouncementsCutout : Paint
|
|
private lateinit var btnColumnSetting : ImageButton
|
|
private lateinit var btnColumnReload : ImageButton
|
|
private lateinit var btnColumnClose : ImageButton
|
|
|
|
private lateinit var flColumnBackground : View
|
|
private lateinit var ivColumnBackgroundImage : ImageView
|
|
private lateinit var llSearch : View
|
|
private lateinit var cbDontCloseColumn : CheckBox
|
|
private lateinit var cbRemoteOnly : CheckBox
|
|
private lateinit var cbWithAttachment : CheckBox
|
|
private lateinit var cbWithHighlight : CheckBox
|
|
private lateinit var cbDontShowBoost : CheckBox
|
|
private lateinit var cbDontShowFollow : CheckBox
|
|
private lateinit var cbDontShowFavourite : CheckBox
|
|
private lateinit var cbDontShowReply : CheckBox
|
|
private lateinit var cbDontShowReaction : CheckBox
|
|
private lateinit var cbDontShowVote : CheckBox
|
|
private lateinit var cbDontShowNormalToot : CheckBox
|
|
private lateinit var cbDontShowNonPublicToot : CheckBox
|
|
private lateinit var cbInstanceLocal : CheckBox
|
|
private lateinit var cbDontStreaming : CheckBox
|
|
private lateinit var cbDontAutoRefresh : CheckBox
|
|
private lateinit var cbHideMediaDefault : CheckBox
|
|
private lateinit var cbSystemNotificationNotRelated : CheckBox
|
|
private lateinit var cbEnableSpeech : CheckBox
|
|
private lateinit var cbOldApi : CheckBox
|
|
private lateinit var llRegexFilter : View
|
|
private lateinit var btnDeleteNotification : Button
|
|
private lateinit var btnColor : Button
|
|
private lateinit var btnLanguageFilter : Button
|
|
|
|
private lateinit var svQuickFilter : HorizontalScrollView
|
|
private lateinit var btnQuickFilterAll : Button
|
|
private lateinit var btnQuickFilterMention : ImageButton
|
|
private lateinit var btnQuickFilterFavourite : ImageButton
|
|
private lateinit var btnQuickFilterBoost : ImageButton
|
|
private lateinit var btnQuickFilterFollow : ImageButton
|
|
private lateinit var btnQuickFilterPost : ImageButton
|
|
|
|
private lateinit var btnQuickFilterReaction : ImageButton
|
|
private lateinit var btnQuickFilterVote : ImageButton
|
|
|
|
private lateinit var llRefreshError : FrameLayout
|
|
private lateinit var ivRefreshError : ImageView
|
|
private lateinit var tvRefreshError : TextView
|
|
|
|
private lateinit var llListList : View
|
|
private lateinit var etListName : EditText
|
|
private lateinit var btnListAdd : View
|
|
|
|
private lateinit var llHashtagExtra : LinearLayout
|
|
private lateinit var etHashtagExtraAny : EditText
|
|
private lateinit var etHashtagExtraAll : EditText
|
|
private lateinit var etHashtagExtraNone : EditText
|
|
|
|
private lateinit var llAnnouncementsBox : View
|
|
private lateinit var tvAnnouncementsCaption : TextView
|
|
private lateinit var tvAnnouncementsIndex : TextView
|
|
private lateinit var btnAnnouncementsPrev : ImageButton
|
|
private lateinit var btnAnnouncementsNext : ImageButton
|
|
private lateinit var llAnnouncements : View
|
|
private lateinit var tvAnnouncementPeriod : TextView
|
|
private lateinit var tvAnnouncementContent : MyTextView
|
|
private lateinit var llAnnouncementExtra : LinearLayout
|
|
|
|
private val announcementContentInvalidator : NetworkEmojiInvalidator
|
|
|
|
var lastAnnouncementShown = 0L
|
|
|
|
private val extra_invalidator_list = ArrayList<NetworkEmojiInvalidator>()
|
|
|
|
private val isPageDestroyed : Boolean
|
|
get() = column == null || activity.isFinishing
|
|
|
|
private var binding_busy : Boolean = false
|
|
|
|
private var last_image_uri : String? = null
|
|
private var last_image_bitmap : Bitmap? = null
|
|
private var last_image_task : Job? = null
|
|
|
|
private fun checkRegexFilterError(src : String) : String? {
|
|
try {
|
|
if(src.isEmpty()) {
|
|
return null
|
|
}
|
|
val m = Pattern.compile(src).matcher("")
|
|
if(m.find()) {
|
|
// 空文字列にマッチする正規表現はエラー扱いにする
|
|
// そうしないとCWの警告テキストにマッチしてしまう
|
|
return activity.getString(R.string.regex_filter_matches_empty_string)
|
|
}
|
|
return null
|
|
} catch(ex : Throwable) {
|
|
val message = ex.message
|
|
return if(message != null && message.isNotEmpty()) {
|
|
message
|
|
} else {
|
|
ex.withCaption(activity.resources, R.string.regex_error)
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun isRegexValid() : Boolean {
|
|
val s = etRegexFilter.text.toString()
|
|
val error = checkRegexFilterError(s)
|
|
tvRegexFilterError.text = error ?: ""
|
|
return error == null
|
|
}
|
|
|
|
val isColumnSettingShown : Boolean
|
|
get() = llColumnSetting.visibility == View.VISIBLE
|
|
|
|
// val headerView : HeaderViewHolderBase?
|
|
// get() = status_adapter?.header
|
|
|
|
val scrollPosition : ScrollPosition
|
|
get() = ScrollPosition(this)
|
|
|
|
inner class ErrorFlickListener(
|
|
context : Context
|
|
) : View.OnTouchListener, GestureDetector.OnGestureListener {
|
|
|
|
private val gd = GestureDetector(context, this)
|
|
private val density = context.resources.displayMetrics.density
|
|
|
|
@SuppressLint("ClickableViewAccessibility")
|
|
override fun onTouch(v : View?, event : MotionEvent?) : Boolean {
|
|
return gd.onTouchEvent(event)
|
|
}
|
|
|
|
override fun onShowPress(e : MotionEvent?) {
|
|
}
|
|
|
|
override fun onLongPress(e : MotionEvent?) {
|
|
}
|
|
|
|
override fun onSingleTapUp(e : MotionEvent?) : Boolean {
|
|
return true
|
|
}
|
|
|
|
override fun onDown(e : MotionEvent?) : Boolean {
|
|
return true
|
|
}
|
|
|
|
override fun onScroll(
|
|
e1 : MotionEvent?,
|
|
e2 : MotionEvent?,
|
|
distanceX : Float,
|
|
distanceY : Float
|
|
) : Boolean {
|
|
return true
|
|
}
|
|
|
|
override fun onFling(
|
|
e1 : MotionEvent?,
|
|
e2 : MotionEvent?,
|
|
velocityX : Float,
|
|
velocityY : Float
|
|
) : Boolean {
|
|
|
|
val vx = velocityX.abs()
|
|
val vy = velocityY.abs()
|
|
if(vy < vx * 1.5f) {
|
|
// フリック方向が上下ではない
|
|
log.d("fling? not vertical view. $vx $vy")
|
|
} else {
|
|
|
|
val vydp = vy / density
|
|
val limit = 1024f
|
|
log.d("fling? $vydp/$limit")
|
|
if(vydp >= limit) {
|
|
|
|
val column = column
|
|
if(column != null && column.lastTask == null) {
|
|
column.startLoading()
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
@SuppressLint("ClickableViewAccessibility")
|
|
private fun initLoadingTextView() {
|
|
tvLoading.setOnTouchListener(ErrorFlickListener(activity))
|
|
}
|
|
|
|
val viewRoot : View = inflate(activity, parent)
|
|
|
|
init {
|
|
|
|
viewRoot.scan { v ->
|
|
try {
|
|
// ボタンではないTextViewのフォントを変更する
|
|
if(v is TextView && v !is Button) {
|
|
v.typeface = ActMain.timeline_font
|
|
}
|
|
} catch(ex : Throwable) {
|
|
log.trace(ex)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if(Pref.bpShareViewPool(activity.pref)) {
|
|
listView.setRecycledViewPool(activity.viewPool)
|
|
}
|
|
listView.itemAnimator = null
|
|
//
|
|
// val animator = listView.itemAnimator
|
|
// if( animator is DefaultItemAnimator){
|
|
// animator.supportsChangeAnimations = false
|
|
// }
|
|
|
|
btnListAdd.setOnClickListener(this)
|
|
|
|
etListName.setOnEditorActionListener { _, actionId, _ ->
|
|
var handled = false
|
|
if(actionId == EditorInfo.IME_ACTION_SEND) {
|
|
btnListAdd.performClick()
|
|
handled = true
|
|
}
|
|
handled
|
|
}
|
|
|
|
btnQuickFilterAll.setOnClickListener(this)
|
|
btnQuickFilterMention.setOnClickListener(this)
|
|
btnQuickFilterFavourite.setOnClickListener(this)
|
|
btnQuickFilterBoost.setOnClickListener(this)
|
|
btnQuickFilterFollow.setOnClickListener(this)
|
|
btnQuickFilterPost.setOnClickListener(this)
|
|
btnQuickFilterReaction.setOnClickListener(this)
|
|
btnQuickFilterVote.setOnClickListener(this)
|
|
|
|
llColumnHeader.setOnClickListener(this)
|
|
btnAnnouncements.setOnClickListener(this)
|
|
btnColumnSetting.setOnClickListener(this)
|
|
btnColumnReload.setOnClickListener(this)
|
|
btnColumnClose.setOnClickListener(this)
|
|
btnColumnClose.setOnLongClickListener(this)
|
|
btnDeleteNotification.setOnClickListener(this)
|
|
|
|
btnColor.setOnClickListener(this)
|
|
btnLanguageFilter.setOnClickListener(this)
|
|
|
|
refreshLayout.setOnRefreshListener(this)
|
|
refreshLayout.setDistanceToTriggerSync((0.5f + 20f * activity.density).toInt())
|
|
|
|
llRefreshError.setOnClickListener(this)
|
|
|
|
btnAnnouncementsPrev.setOnClickListener(this)
|
|
btnAnnouncementsNext.setOnClickListener(this)
|
|
|
|
cbDontCloseColumn.setOnCheckedChangeListener(this)
|
|
cbRemoteOnly.setOnCheckedChangeListener(this)
|
|
cbWithAttachment.setOnCheckedChangeListener(this)
|
|
cbWithHighlight.setOnCheckedChangeListener(this)
|
|
cbDontShowBoost.setOnCheckedChangeListener(this)
|
|
cbDontShowFollow.setOnCheckedChangeListener(this)
|
|
cbDontShowFavourite.setOnCheckedChangeListener(this)
|
|
cbDontShowReply.setOnCheckedChangeListener(this)
|
|
cbDontShowReaction.setOnCheckedChangeListener(this)
|
|
cbDontShowVote.setOnCheckedChangeListener(this)
|
|
cbDontShowNormalToot.setOnCheckedChangeListener(this)
|
|
cbDontShowNonPublicToot.setOnCheckedChangeListener(this)
|
|
cbInstanceLocal.setOnCheckedChangeListener(this)
|
|
cbDontStreaming.setOnCheckedChangeListener(this)
|
|
cbDontAutoRefresh.setOnCheckedChangeListener(this)
|
|
cbHideMediaDefault.setOnCheckedChangeListener(this)
|
|
cbSystemNotificationNotRelated.setOnCheckedChangeListener(this)
|
|
cbEnableSpeech.setOnCheckedChangeListener(this)
|
|
cbOldApi.setOnCheckedChangeListener(this)
|
|
|
|
if(Pref.bpMoveNotificationsQuickFilter(activity.pref)) {
|
|
(svQuickFilter.parent as? ViewGroup)?.removeView(svQuickFilter)
|
|
llColumnSettingInside.addView(svQuickFilter, 0)
|
|
|
|
svQuickFilter.setOnTouchListener { v, event ->
|
|
val action = event.action
|
|
if(action == MotionEvent.ACTION_DOWN) {
|
|
val sv = v as? HorizontalScrollView
|
|
if(sv != null && sv.getChildAt(0).width > sv.width) {
|
|
sv.requestDisallowInterceptTouchEvent(true)
|
|
}
|
|
}
|
|
v.onTouchEvent(event)
|
|
}
|
|
}
|
|
|
|
if(! activity.header_text_size_sp.isNaN()) {
|
|
tvColumnName.textSize = activity.header_text_size_sp
|
|
|
|
val acctSize = activity.header_text_size_sp * 0.857f
|
|
tvColumnContext.textSize = acctSize
|
|
tvColumnStatus.textSize = acctSize
|
|
tvColumnIndex.textSize = acctSize
|
|
}
|
|
|
|
initLoadingTextView()
|
|
|
|
var pad = 0
|
|
var wh = ActMain.headerIconSize + pad * 2
|
|
ivColumnIcon.layoutParams.width = wh
|
|
ivColumnIcon.layoutParams.height = wh
|
|
ivColumnIcon.setPaddingRelative(pad, pad, pad, pad)
|
|
|
|
pad = (ActMain.headerIconSize * 0.125f + 0.5f).toInt()
|
|
wh = ActMain.headerIconSize + pad * 2
|
|
|
|
btnAnnouncements.layoutParams.width = wh
|
|
btnAnnouncements.layoutParams.height = wh
|
|
btnAnnouncements.setPaddingRelative(pad, pad, pad, pad)
|
|
|
|
btnColumnSetting.layoutParams.width = wh
|
|
btnColumnSetting.layoutParams.height = wh
|
|
btnColumnSetting.setPaddingRelative(pad, pad, pad, pad)
|
|
btnColumnReload.layoutParams.width = wh
|
|
btnColumnReload.layoutParams.height = wh
|
|
btnColumnReload.setPaddingRelative(pad, pad, pad, pad)
|
|
btnColumnClose.layoutParams.width = wh
|
|
btnColumnClose.layoutParams.height = wh
|
|
btnColumnClose.setPaddingRelative(pad, pad, pad, pad)
|
|
|
|
btnSearch.setOnClickListener(this)
|
|
btnSearchClear.setOnClickListener(this)
|
|
etSearch.setOnEditorActionListener(TextView.OnEditorActionListener { _, actionId, _ ->
|
|
if(! binding_busy) {
|
|
if(actionId == EditorInfo.IME_ACTION_SEARCH) {
|
|
btnSearch.performClick()
|
|
return@OnEditorActionListener true
|
|
}
|
|
}
|
|
false
|
|
})
|
|
|
|
// 入力の追跡
|
|
etRegexFilter.addTextChangedListener(CustomTextWatcher {
|
|
if(binding_busy || isPageDestroyed) return@CustomTextWatcher
|
|
if(! isRegexValid()) return@CustomTextWatcher
|
|
column?.regex_text = etRegexFilter.text.toString()
|
|
activity.app_state.saveColumnList()
|
|
activity.handler.removeCallbacks(proc_start_filter)
|
|
activity.handler.postDelayed(proc_start_filter, 666L)
|
|
})
|
|
|
|
etHashtagExtraAny.addTextChangedListener(CustomTextWatcher {
|
|
if(binding_busy || isPageDestroyed) return@CustomTextWatcher
|
|
column?.hashtag_any = etHashtagExtraAny.text.toString()
|
|
activity.app_state.saveColumnList()
|
|
activity.handler.removeCallbacks(proc_start_filter)
|
|
activity.handler.postDelayed(proc_start_filter, 666L)
|
|
})
|
|
|
|
etHashtagExtraAll.addTextChangedListener(CustomTextWatcher {
|
|
if(binding_busy || isPageDestroyed) return@CustomTextWatcher
|
|
column?.hashtag_all = etHashtagExtraAll.text.toString()
|
|
activity.app_state.saveColumnList()
|
|
activity.handler.removeCallbacks(proc_start_filter)
|
|
activity.handler.postDelayed(proc_start_filter, 666L)
|
|
})
|
|
|
|
etHashtagExtraNone.addTextChangedListener(CustomTextWatcher {
|
|
if(binding_busy || isPageDestroyed) return@CustomTextWatcher
|
|
column?.hashtag_none = etHashtagExtraNone.text.toString()
|
|
activity.app_state.saveColumnList()
|
|
activity.handler.removeCallbacks(proc_start_filter)
|
|
activity.handler.postDelayed(proc_start_filter, 666L)
|
|
})
|
|
|
|
announcementContentInvalidator =
|
|
NetworkEmojiInvalidator(activity.handler, tvAnnouncementContent)
|
|
tvAnnouncementContent.movementMethod = MyLinkMovementMethod
|
|
}
|
|
|
|
private val proc_start_filter : Runnable = Runnable {
|
|
if(binding_busy || isPageDestroyed) return@Runnable
|
|
column?.startLoading()
|
|
}
|
|
|
|
private val proc_restoreScrollPosition = object : Runnable {
|
|
override fun run() {
|
|
activity.handler.removeCallbacks(this)
|
|
|
|
if(isPageDestroyed) {
|
|
log.d("restoreScrollPosition [%d], page is destroyed.")
|
|
return
|
|
}
|
|
|
|
val column = this@ColumnViewHolder.column
|
|
if(column == null) {
|
|
log.d("restoreScrollPosition [%d], column==null", page_idx)
|
|
return
|
|
}
|
|
|
|
if(column.is_dispose.get()) {
|
|
log.d("restoreScrollPosition [%d], column is disposed", page_idx)
|
|
return
|
|
}
|
|
|
|
if(column.hasMultipleViewHolder()) {
|
|
log.d(
|
|
"restoreScrollPosition [%d] %s , column has multiple view holder. retry later.",
|
|
page_idx,
|
|
column.getColumnName(true)
|
|
)
|
|
|
|
// タブレットモードでカラムを追加/削除した際に発生する。
|
|
// このタイミングでスクロール位置を復元してもうまくいかないので延期する
|
|
activity.handler.postDelayed(this, 100L)
|
|
return
|
|
}
|
|
|
|
//復元後にもここを通るがこれは正常である
|
|
val sp = column.scroll_save
|
|
if(sp == null) {
|
|
// val lvi = column.last_viewing_item_id
|
|
// if( lvi != null ){
|
|
// column.last_viewing_item_id = null
|
|
// val listIndex = column.findListIndexByTimelineId(lvi)
|
|
// if( listIndex != null){
|
|
// log.d(
|
|
// "restoreScrollPosition [$page_idx] %s , restore from last_viewing_item_id %d"
|
|
// , column.getColumnName( true )
|
|
// ,listIndex
|
|
// )
|
|
// ScrollPosition(column.toAdapterIndex(listIndex),0).restore(this@ColumnViewHolder)
|
|
// return
|
|
// }
|
|
// }
|
|
|
|
log.d(
|
|
"restoreScrollPosition [$page_idx] %s , column has no saved scroll position."
|
|
, column.getColumnName(true)
|
|
)
|
|
return
|
|
}
|
|
|
|
column.scroll_save = null
|
|
|
|
if(listView.visibility != View.VISIBLE) {
|
|
log.d(
|
|
"restoreScrollPosition [$page_idx] %s , listView is not visible. saved position %s,%s is dropped."
|
|
, column.getColumnName(true)
|
|
, sp.adapterIndex
|
|
, sp.offset
|
|
)
|
|
} else {
|
|
log.d(
|
|
"restoreScrollPosition [%d] %s , listView is visible. resume %s,%s"
|
|
, page_idx
|
|
, column.getColumnName(true)
|
|
, sp.adapterIndex
|
|
, sp.offset
|
|
)
|
|
sp.restore(this@ColumnViewHolder)
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
fun onPageDestroy(page_idx : Int) {
|
|
// タブレットモードの場合、onPageCreateより前に呼ばれる
|
|
val column = this@ColumnViewHolder.column
|
|
if(column != null) {
|
|
log.d("onPageDestroy [%d] %s", page_idx, tvColumnName.text)
|
|
saveScrollPosition()
|
|
listView.adapter = null
|
|
column.removeColumnViewHolder(this)
|
|
this@ColumnViewHolder.column = null
|
|
}
|
|
|
|
closeBitmaps()
|
|
|
|
activity.closeListItemPopup()
|
|
}
|
|
|
|
fun onPageCreate(column : Column, page_idx : Int, page_count : Int) {
|
|
binding_busy = true
|
|
try {
|
|
this.column = column
|
|
this.page_idx = page_idx
|
|
|
|
log.d("onPageCreate [%d] %s", page_idx, column.getColumnName(true))
|
|
|
|
val bSimpleList =
|
|
column.type != ColumnType.CONVERSATION && Pref.bpSimpleList(activity.pref)
|
|
|
|
tvColumnIndex.text = activity.getString(R.string.column_index, page_idx + 1, page_count)
|
|
tvColumnStatus.text = "?"
|
|
ivColumnIcon.setImageResource(column.getIconId())
|
|
|
|
listView.adapter = null
|
|
if(listView.itemDecorationCount == 0) {
|
|
listView.addItemDecoration(ListDivider(activity))
|
|
}
|
|
|
|
val status_adapter = ItemListAdapter(activity, column, this, bSimpleList)
|
|
this.status_adapter = status_adapter
|
|
|
|
val isNotificationColumn = column.isNotificationColumn
|
|
|
|
// 添付メディアや正規表現のフィルタ
|
|
val bAllowFilter = column.canStatusFilter()
|
|
|
|
showColumnSetting(false)
|
|
|
|
|
|
|
|
cbDontCloseColumn.isCheckedNoAnime = column.dont_close
|
|
cbRemoteOnly.isCheckedNoAnime = column.remote_only
|
|
cbWithAttachment.isCheckedNoAnime = column.with_attachment
|
|
cbWithHighlight.isCheckedNoAnime = column.with_highlight
|
|
cbDontShowBoost.isCheckedNoAnime = column.dont_show_boost
|
|
cbDontShowFollow.isCheckedNoAnime = column.dont_show_follow
|
|
cbDontShowFavourite.isCheckedNoAnime = column.dont_show_favourite
|
|
cbDontShowReply.isCheckedNoAnime = column.dont_show_reply
|
|
cbDontShowReaction.isCheckedNoAnime = column.dont_show_reaction
|
|
cbDontShowVote.isCheckedNoAnime = column.dont_show_vote
|
|
cbDontShowNormalToot.isCheckedNoAnime = column.dont_show_normal_toot
|
|
cbDontShowNonPublicToot.isCheckedNoAnime = column.dont_show_non_public_toot
|
|
cbInstanceLocal.isCheckedNoAnime = column.instance_local
|
|
cbDontStreaming.isCheckedNoAnime = column.dont_streaming
|
|
cbDontAutoRefresh.isCheckedNoAnime = column.dont_auto_refresh
|
|
cbHideMediaDefault.isCheckedNoAnime = column.hide_media_default
|
|
cbSystemNotificationNotRelated.isCheckedNoAnime = column.system_notification_not_related
|
|
cbEnableSpeech.isCheckedNoAnime = column.enable_speech
|
|
cbOldApi.isCheckedNoAnime = column.use_old_api
|
|
|
|
etRegexFilter.setText(column.regex_text)
|
|
etSearch.setText(column.search_query)
|
|
cbResolve.isCheckedNoAnime = column.search_resolve
|
|
|
|
cbRemoteOnly.vg(column.canRemoteOnly())
|
|
|
|
cbWithAttachment.vg(bAllowFilter)
|
|
cbWithHighlight.vg(bAllowFilter)
|
|
etRegexFilter.vg(bAllowFilter)
|
|
llRegexFilter.vg(bAllowFilter)
|
|
btnLanguageFilter.vg(bAllowFilter)
|
|
|
|
cbDontShowBoost.vg(column.canFilterBoost())
|
|
cbDontShowReply.vg(column.canFilterReply())
|
|
cbDontShowNormalToot.vg(column.canFilterNormalToot())
|
|
cbDontShowNonPublicToot.vg(column.canFilterNonPublicToot())
|
|
cbDontShowReaction.vg(isNotificationColumn && column.isMisskey)
|
|
cbDontShowVote.vg(isNotificationColumn)
|
|
cbDontShowFavourite.vg(isNotificationColumn && ! column.isMisskey)
|
|
cbDontShowFollow.vg(isNotificationColumn)
|
|
|
|
cbInstanceLocal.vg(column.type == ColumnType.HASHTAG)
|
|
|
|
|
|
cbDontStreaming.vg(column.canStreaming())
|
|
cbDontAutoRefresh.vg(column.canAutoRefresh())
|
|
cbHideMediaDefault.vg(column.canNSFWDefault())
|
|
cbSystemNotificationNotRelated.vg(column.isNotificationColumn)
|
|
cbEnableSpeech.vg(column.canSpeech())
|
|
cbOldApi.vg(column.type == ColumnType.DIRECT_MESSAGES)
|
|
|
|
|
|
btnDeleteNotification.vg(column.isNotificationColumn)
|
|
|
|
llSearch.vg(column.isSearchColumn)?.let {
|
|
btnSearchClear.vg(Pref.bpShowSearchClear(activity.pref))
|
|
}
|
|
|
|
llListList.vg(column.type == ColumnType.LIST_LIST)
|
|
cbResolve.vg(column.type == ColumnType.SEARCH)
|
|
|
|
llHashtagExtra.vg(column.hasHashtagExtra())
|
|
etHashtagExtraAny.setText(column.hashtag_any)
|
|
etHashtagExtraAll.setText(column.hashtag_all)
|
|
etHashtagExtraNone.setText(column.hashtag_none)
|
|
|
|
// tvRegexFilterErrorの表示を更新
|
|
if(bAllowFilter) {
|
|
isRegexValid()
|
|
}
|
|
|
|
val canRefreshTop = column.canRefreshTopBySwipe()
|
|
val canRefreshBottom = column.canRefreshBottomBySwipe()
|
|
|
|
refreshLayout.isEnabled = canRefreshTop || canRefreshBottom
|
|
refreshLayout.direction = if(canRefreshTop && canRefreshBottom) {
|
|
SwipyRefreshLayoutDirection.BOTH
|
|
} else if(canRefreshTop) {
|
|
SwipyRefreshLayoutDirection.TOP
|
|
} else {
|
|
SwipyRefreshLayoutDirection.BOTTOM
|
|
}
|
|
|
|
bRefreshErrorWillShown = false
|
|
llRefreshError.clearAnimation()
|
|
llRefreshError.visibility = View.GONE
|
|
|
|
//
|
|
listView.adapter = status_adapter
|
|
|
|
//XXX FastScrollerのサポートを諦める。ライブラリはいくつかあるんだけど、設定でON/OFFできなかったり頭文字バブルを無効にできなかったり
|
|
// listView.isFastScrollEnabled = ! Pref.bpDisableFastScroller(Pref.pref(activity))
|
|
|
|
column.addColumnViewHolder(this)
|
|
|
|
lastAnnouncementShown = - 1L
|
|
|
|
fun dip(dp : Int) : Int = (activity.density * dp + 0.5f).toInt()
|
|
val context = activity
|
|
|
|
val announcementsBgColor = Pref.ipAnnouncementsBgColor(activity.pref).notZero()
|
|
?: getAttributeColor(context, R.attr.colorSearchFormBackground)
|
|
|
|
btnAnnouncementsCutout.apply {
|
|
color = announcementsBgColor
|
|
}
|
|
|
|
llAnnouncementsBox.apply {
|
|
background = createRoundDrawable( dip(6).toFloat(), announcementsBgColor )
|
|
val pad_tb = dip(2)
|
|
setPadding(0, pad_tb, 0, pad_tb)
|
|
}
|
|
|
|
val searchBgColor = Pref.ipSearchBgColor(activity.pref).notZero()
|
|
?: getAttributeColor(context, R.attr.colorSearchFormBackground)
|
|
|
|
llSearch.apply {
|
|
backgroundColor = searchBgColor
|
|
startPadding = dip(12)
|
|
endPadding = dip(12)
|
|
topPadding = dip(3)
|
|
bottomPadding = dip(3)
|
|
}
|
|
|
|
llListList.apply {
|
|
backgroundColor = searchBgColor
|
|
startPadding = dip(12)
|
|
endPadding = dip(12)
|
|
topPadding = dip(3)
|
|
bottomPadding = dip(3)
|
|
}
|
|
|
|
showColumnColor()
|
|
|
|
showContent(reason = "onPageCreate", reset = true)
|
|
} finally {
|
|
binding_busy = false
|
|
}
|
|
}
|
|
|
|
private val procShowColumnStatus : Runnable = Runnable {
|
|
|
|
val column = this.column
|
|
if(column == null || column.is_dispose.get()) return@Runnable
|
|
|
|
val sb = SpannableStringBuilder()
|
|
try {
|
|
|
|
val task = column.lastTask
|
|
if(task != null) {
|
|
sb.append(task.ctType.marker) // L,T,B,G
|
|
sb.append(
|
|
when {
|
|
task.isCancelled -> "~"
|
|
task.ctClosed.get() -> "!"
|
|
task.ctStarted.get() -> ""
|
|
else -> "?"
|
|
}
|
|
)
|
|
}
|
|
when(column.getStreamingStatus()) {
|
|
StreamingIndicatorState.NONE -> {
|
|
}
|
|
|
|
StreamingIndicatorState.REGISTERED -> {
|
|
sb.appendColorShadeIcon(activity, R.drawable.ic_pulse, "Streaming")
|
|
sb.append("?")
|
|
}
|
|
|
|
StreamingIndicatorState.LISTENING -> {
|
|
sb.appendColorShadeIcon(activity, R.drawable.ic_pulse, "Streaming")
|
|
}
|
|
}
|
|
|
|
} finally {
|
|
log.d("showColumnStatus ${sb}")
|
|
tvColumnStatus.text = sb
|
|
}
|
|
}
|
|
|
|
fun showColumnStatus() {
|
|
activity.handler.removeCallbacks(procShowColumnStatus)
|
|
activity.handler.postDelayed(procShowColumnStatus, 50L)
|
|
}
|
|
|
|
fun showColumnColor() {
|
|
val column = this.column
|
|
if(column == null || column.is_dispose.get()) return
|
|
|
|
// カラムヘッダ背景
|
|
column.setHeaderBackground(llColumnHeader)
|
|
|
|
// カラムヘッダ文字色(A)
|
|
var c = column.getHeaderNameColor()
|
|
val csl = ColorStateList.valueOf(c)
|
|
tvColumnName.textColor = c
|
|
ivColumnIcon.imageTintList = csl
|
|
btnAnnouncements.imageTintList = csl
|
|
btnColumnSetting.imageTintList = csl
|
|
btnColumnReload.imageTintList = csl
|
|
btnColumnClose.imageTintList = csl
|
|
|
|
// カラムヘッダ文字色(B)
|
|
c = column.getHeaderPageNumberColor()
|
|
tvColumnIndex.textColor = c
|
|
tvColumnStatus.textColor = c
|
|
|
|
// カラム内部の背景色
|
|
flColumnBackground.setBackgroundColor(
|
|
column.column_bg_color.notZero()
|
|
?: Column.defaultColorContentBg
|
|
)
|
|
|
|
// カラム内部の背景画像
|
|
ivColumnBackgroundImage.alpha = column.column_bg_image_alpha
|
|
loadBackgroundImage(ivColumnBackgroundImage, column.column_bg_image)
|
|
|
|
// エラー表示
|
|
tvLoading.textColor = column.getContentColor()
|
|
|
|
status_adapter?.findHeaderViewHolder(listView)?.showColor()
|
|
|
|
// カラム色を変更したらクイックフィルタの色も変わる場合がある
|
|
showQuickFilter()
|
|
|
|
showAnnouncements(force = false)
|
|
}
|
|
|
|
private fun closeBitmaps() {
|
|
try {
|
|
ivColumnBackgroundImage.visibility = View.GONE
|
|
ivColumnBackgroundImage.setImageDrawable(null)
|
|
|
|
last_image_bitmap?.recycle()
|
|
last_image_bitmap = null
|
|
|
|
last_image_task?.cancel()
|
|
last_image_task = null
|
|
|
|
last_image_uri = null
|
|
|
|
} catch(ex : Throwable) {
|
|
log.trace(ex)
|
|
}
|
|
|
|
}
|
|
|
|
@SuppressLint("StaticFieldLeak")
|
|
private fun loadBackgroundImage(iv : ImageView, url : String?) {
|
|
try {
|
|
if(url == null || url.isEmpty() || Pref.bpDontShowColumnBackgroundImage(activity.pref)) {
|
|
// 指定がないなら閉じる
|
|
closeBitmaps()
|
|
return
|
|
}
|
|
|
|
if(url == last_image_uri) {
|
|
// 今表示してるのと同じ
|
|
return
|
|
}
|
|
|
|
// 直前の処理をキャンセルする。Bitmapも破棄する
|
|
closeBitmaps()
|
|
|
|
// ロード開始
|
|
last_image_uri = url
|
|
val screen_w = iv.resources.displayMetrics.widthPixels
|
|
val screen_h = iv.resources.displayMetrics.heightPixels
|
|
|
|
// 非同期処理を開始
|
|
last_image_task = GlobalScope.launch(Dispatchers.Main) {
|
|
val bitmap = try {
|
|
withContext(Dispatchers.IO) {
|
|
try {
|
|
createResizedBitmap(
|
|
activity, url.toUri(),
|
|
if(screen_w > screen_h)
|
|
screen_w
|
|
else
|
|
screen_h
|
|
)
|
|
} catch(ex : Throwable) {
|
|
log.trace(ex)
|
|
null
|
|
}
|
|
}
|
|
} catch(ex : Throwable) {
|
|
null
|
|
}
|
|
if(bitmap != null) {
|
|
if(! coroutineContext.isActive || url != last_image_uri) {
|
|
bitmap.recycle()
|
|
} else {
|
|
last_image_bitmap = bitmap
|
|
iv.setImageBitmap(last_image_bitmap)
|
|
iv.visibility = View.VISIBLE
|
|
}
|
|
}
|
|
}
|
|
} catch(ex : Throwable) {
|
|
log.trace(ex)
|
|
}
|
|
|
|
}
|
|
|
|
fun showColumnSetting(show : Boolean) : Boolean {
|
|
llColumnSetting.vg(show)
|
|
llColumnHeader.invalidate()
|
|
return show
|
|
}
|
|
|
|
fun onListListUpdated() {
|
|
etListName.setText("")
|
|
}
|
|
|
|
override fun onRefresh(direction : SwipyRefreshLayoutDirection) {
|
|
val column = this.column ?: return
|
|
|
|
// カラムを追加/削除したときに ColumnからColumnViewHolderへの参照が外れることがある
|
|
// リロードやリフレッシュ操作で直るようにする
|
|
column.addColumnViewHolder(this)
|
|
|
|
if(direction == SwipyRefreshLayoutDirection.TOP && column.canReloadWhenRefreshTop()) {
|
|
refreshLayout.isRefreshing = false
|
|
activity.handler.post {
|
|
this@ColumnViewHolder.column?.startLoading()
|
|
}
|
|
return
|
|
}
|
|
|
|
column.startRefresh(false, direction == SwipyRefreshLayoutDirection.BOTTOM)
|
|
}
|
|
|
|
override fun onCheckedChanged(view : CompoundButton, isChecked : Boolean) {
|
|
val column = this@ColumnViewHolder.column
|
|
|
|
if(binding_busy || column == null || status_adapter == null) return
|
|
|
|
// カラムを追加/削除したときに ColumnからColumnViewHolderへの参照が外れることがある
|
|
// リロードやリフレッシュ操作で直るようにする
|
|
column.addColumnViewHolder(this)
|
|
|
|
when(view) {
|
|
|
|
cbDontCloseColumn -> {
|
|
column.dont_close = isChecked
|
|
showColumnCloseButton()
|
|
activity.app_state.saveColumnList()
|
|
}
|
|
|
|
cbWithAttachment -> {
|
|
column.with_attachment = isChecked
|
|
activity.app_state.saveColumnList()
|
|
column.startLoading()
|
|
}
|
|
|
|
cbRemoteOnly -> {
|
|
column.remote_only = isChecked
|
|
activity.app_state.saveColumnList()
|
|
column.startLoading()
|
|
}
|
|
|
|
cbWithHighlight -> {
|
|
column.with_highlight = isChecked
|
|
activity.app_state.saveColumnList()
|
|
column.startLoading()
|
|
}
|
|
|
|
cbDontShowBoost -> {
|
|
column.dont_show_boost = isChecked
|
|
activity.app_state.saveColumnList()
|
|
column.startLoading()
|
|
}
|
|
|
|
cbDontShowReply -> {
|
|
column.dont_show_reply = isChecked
|
|
activity.app_state.saveColumnList()
|
|
column.startLoading()
|
|
}
|
|
|
|
cbDontShowReaction -> {
|
|
column.dont_show_reaction = isChecked
|
|
activity.app_state.saveColumnList()
|
|
column.startLoading()
|
|
}
|
|
|
|
cbDontShowVote -> {
|
|
column.dont_show_vote = isChecked
|
|
activity.app_state.saveColumnList()
|
|
column.startLoading()
|
|
}
|
|
|
|
cbDontShowNormalToot -> {
|
|
column.dont_show_normal_toot = isChecked
|
|
activity.app_state.saveColumnList()
|
|
column.startLoading()
|
|
}
|
|
|
|
cbDontShowNonPublicToot -> {
|
|
column.dont_show_non_public_toot = isChecked
|
|
activity.app_state.saveColumnList()
|
|
column.startLoading()
|
|
}
|
|
|
|
cbDontShowFavourite -> {
|
|
column.dont_show_favourite = isChecked
|
|
activity.app_state.saveColumnList()
|
|
column.startLoading()
|
|
}
|
|
|
|
cbDontShowFollow -> {
|
|
column.dont_show_follow = isChecked
|
|
activity.app_state.saveColumnList()
|
|
column.startLoading()
|
|
}
|
|
|
|
cbInstanceLocal -> {
|
|
column.instance_local = isChecked
|
|
activity.app_state.saveColumnList()
|
|
column.startLoading()
|
|
}
|
|
|
|
cbDontStreaming -> {
|
|
column.dont_streaming = isChecked
|
|
activity.app_state.saveColumnList()
|
|
if(isChecked) {
|
|
column.stopStreaming()
|
|
} else {
|
|
column.onStart(activity)
|
|
}
|
|
}
|
|
|
|
cbDontAutoRefresh -> {
|
|
column.dont_auto_refresh = isChecked
|
|
activity.app_state.saveColumnList()
|
|
}
|
|
|
|
cbHideMediaDefault -> {
|
|
column.hide_media_default = isChecked
|
|
activity.app_state.saveColumnList()
|
|
column.fireShowContent(reason = "HideMediaDefault in ColumnSetting", reset = true)
|
|
}
|
|
|
|
cbSystemNotificationNotRelated -> {
|
|
column.system_notification_not_related = isChecked
|
|
activity.app_state.saveColumnList()
|
|
}
|
|
|
|
cbEnableSpeech -> {
|
|
column.enable_speech = isChecked
|
|
activity.app_state.saveColumnList()
|
|
}
|
|
|
|
cbOldApi -> {
|
|
column.use_old_api = isChecked
|
|
activity.app_state.saveColumnList()
|
|
column.startLoading()
|
|
}
|
|
}
|
|
}
|
|
|
|
override fun onClick(v : View) {
|
|
val column = this.column
|
|
val status_adapter = this.status_adapter
|
|
if(binding_busy || column == null || status_adapter == null) return
|
|
|
|
// カラムを追加/削除したときに ColumnからColumnViewHolderへの参照が外れることがある
|
|
// リロードやリフレッシュ操作で直るようにする
|
|
column.addColumnViewHolder(this)
|
|
|
|
when(v) {
|
|
btnColumnClose -> activity.closeColumn(column)
|
|
|
|
btnColumnReload -> {
|
|
App1.custom_emoji_cache.clearErrorCache()
|
|
|
|
if(column.isSearchColumn) {
|
|
etSearch.hideKeyboard()
|
|
etSearch.setText(column.search_query)
|
|
cbResolve.isCheckedNoAnime = column.search_resolve
|
|
}
|
|
refreshLayout.isRefreshing = false
|
|
column.startLoading()
|
|
}
|
|
|
|
btnSearch -> {
|
|
etSearch.hideKeyboard()
|
|
column.search_query = etSearch.text.toString().trim { it <= ' ' }
|
|
column.search_resolve = cbResolve.isChecked
|
|
activity.app_state.saveColumnList()
|
|
column.startLoading()
|
|
}
|
|
|
|
btnSearchClear -> {
|
|
etSearch.setText("")
|
|
column.search_query = ""
|
|
column.search_resolve = cbResolve.isChecked
|
|
activity.app_state.saveColumnList()
|
|
column.startLoading()
|
|
}
|
|
|
|
llColumnHeader -> scrollToTop2()
|
|
|
|
btnColumnSetting -> {
|
|
if(showColumnSetting(! isColumnSettingShown)) {
|
|
hideAnnouncements()
|
|
}
|
|
}
|
|
|
|
btnDeleteNotification -> Action_Notification.deleteAll(
|
|
activity,
|
|
column.access_info,
|
|
false
|
|
)
|
|
|
|
btnColor -> {
|
|
val idx = activity.app_state.column_list.indexOf(column)
|
|
ActColumnCustomize.open(activity, idx, ActMain.REQUEST_CODE_COLUMN_COLOR)
|
|
}
|
|
|
|
btnLanguageFilter -> {
|
|
val idx = activity.app_state.column_list.indexOf(column)
|
|
ActLanguageFilter.open(activity, idx, ActMain.REQUEST_CODE_LANGUAGE_FILTER)
|
|
}
|
|
|
|
btnListAdd -> {
|
|
val tv = etListName.text.toString().trim { it <= ' ' }
|
|
if(tv.isEmpty()) {
|
|
showToast(activity, true, R.string.list_name_empty)
|
|
return
|
|
}
|
|
Action_List.create(activity, column.access_info, tv, null)
|
|
}
|
|
|
|
llRefreshError -> {
|
|
column.mRefreshLoadingErrorPopupState = 1 - column.mRefreshLoadingErrorPopupState
|
|
showRefreshError()
|
|
}
|
|
|
|
btnQuickFilterAll -> clickQuickFilter(Column.QUICK_FILTER_ALL)
|
|
btnQuickFilterMention -> clickQuickFilter(Column.QUICK_FILTER_MENTION)
|
|
btnQuickFilterFavourite -> clickQuickFilter(Column.QUICK_FILTER_FAVOURITE)
|
|
btnQuickFilterBoost -> clickQuickFilter(Column.QUICK_FILTER_BOOST)
|
|
btnQuickFilterFollow -> clickQuickFilter(Column.QUICK_FILTER_FOLLOW)
|
|
btnQuickFilterPost-> clickQuickFilter(Column.QUICK_FILTER_POST)
|
|
btnQuickFilterReaction -> clickQuickFilter(Column.QUICK_FILTER_REACTION)
|
|
btnQuickFilterVote -> clickQuickFilter(Column.QUICK_FILTER_VOTE)
|
|
|
|
btnAnnouncements -> toggleAnnouncements()
|
|
|
|
btnAnnouncementsPrev -> {
|
|
column.announcementId =
|
|
TootAnnouncement.move(column.announcements, column.announcementId, - 1)
|
|
activity.app_state.saveColumnList()
|
|
showAnnouncements()
|
|
}
|
|
|
|
btnAnnouncementsNext -> {
|
|
column.announcementId =
|
|
TootAnnouncement.move(column.announcements, column.announcementId, + 1)
|
|
activity.app_state.saveColumnList()
|
|
showAnnouncements()
|
|
}
|
|
}
|
|
}
|
|
|
|
override fun onLongClick(v : View) : Boolean {
|
|
return when(v) {
|
|
btnColumnClose -> {
|
|
val idx = activity.app_state.column_list.indexOf(column)
|
|
activity.closeColumnAll(idx)
|
|
true
|
|
}
|
|
|
|
else -> false
|
|
}
|
|
}
|
|
|
|
private fun showError(message : String) {
|
|
hideRefreshError()
|
|
tvLoading.visibility = View.VISIBLE
|
|
tvLoading.text = message
|
|
|
|
refreshLayout.isRefreshing = false
|
|
refreshLayout.visibility = View.GONE
|
|
|
|
}
|
|
|
|
private fun showColumnCloseButton() {
|
|
val dont_close = column?.dont_close ?: return
|
|
btnColumnClose.isEnabled = ! dont_close
|
|
btnColumnClose.alpha = if(dont_close) 0.3f else 1f
|
|
}
|
|
|
|
// 相対時刻を更新する
|
|
fun updateRelativeTime() = rebindAdapterItems()
|
|
|
|
fun rebindAdapterItems() {
|
|
for(childIndex in 0 until listView.childCount) {
|
|
val adapterIndex = listView.getChildAdapterPosition(listView.getChildAt(childIndex))
|
|
if(adapterIndex == RecyclerView.NO_POSITION) continue
|
|
status_adapter?.notifyItemChanged(adapterIndex)
|
|
}
|
|
}
|
|
|
|
private val procShowColumnHeader : Runnable = Runnable {
|
|
|
|
val column = this.column
|
|
if(column == null || column.is_dispose.get()) return@Runnable
|
|
|
|
val ac = AcctColor.load(column.access_info)
|
|
|
|
tvColumnContext.text = ac.nickname
|
|
tvColumnContext.setTextColor(
|
|
ac.color_fg.notZero()
|
|
?: getAttributeColor(activity, R.attr.colorTimeSmall)
|
|
)
|
|
|
|
tvColumnContext.setBackgroundColor(ac.color_bg)
|
|
tvColumnContext.setPaddingRelative(activity.acct_pad_lr, 0, activity.acct_pad_lr, 0)
|
|
|
|
tvColumnName.text = column.getColumnName(false)
|
|
|
|
showColumnCloseButton()
|
|
|
|
showAnnouncements(force = false)
|
|
}
|
|
|
|
// カラムヘッダなど、負荷が低い部分の表示更新
|
|
fun showColumnHeader() {
|
|
activity.handler.removeCallbacks(procShowColumnHeader)
|
|
activity.handler.postDelayed(procShowColumnHeader, 50L)
|
|
|
|
}
|
|
|
|
internal fun showContent(
|
|
reason : String,
|
|
changeList : List<AdapterChange>? = null,
|
|
reset : Boolean = false
|
|
) {
|
|
// クラッシュレポートにadapterとリストデータの状態不整合が多かったので、
|
|
// とりあえずリストデータ変更の通知だけは最優先で行っておく
|
|
try {
|
|
status_adapter?.notifyChange(reason, changeList, reset)
|
|
} catch(ex : Throwable) {
|
|
log.trace(ex)
|
|
}
|
|
|
|
showColumnHeader()
|
|
showColumnStatus()
|
|
|
|
val column = this.column
|
|
if(column == null || column.is_dispose.get()) {
|
|
showError("column was disposed.")
|
|
return
|
|
}
|
|
|
|
if(! column.bFirstInitialized) {
|
|
showError("initializing")
|
|
return
|
|
}
|
|
|
|
if(column.bInitialLoading) {
|
|
var message : String? = column.task_progress
|
|
if(message == null) message = "loading?"
|
|
showError(message)
|
|
return
|
|
}
|
|
|
|
val initialLoadingError = column.mInitialLoadingError
|
|
if(initialLoadingError.isNotEmpty()) {
|
|
showError(initialLoadingError)
|
|
return
|
|
}
|
|
|
|
val status_adapter = this.status_adapter
|
|
|
|
if(status_adapter == null || status_adapter.itemCount == 0) {
|
|
showError(activity.getString(R.string.list_empty))
|
|
return
|
|
}
|
|
|
|
tvLoading.visibility = View.GONE
|
|
|
|
refreshLayout.visibility = View.VISIBLE
|
|
|
|
status_adapter.findHeaderViewHolder(listView)?.bindData(column)
|
|
|
|
if(column.bRefreshLoading) {
|
|
hideRefreshError()
|
|
} else {
|
|
refreshLayout.isRefreshing = false
|
|
showRefreshError()
|
|
}
|
|
proc_restoreScrollPosition.run()
|
|
|
|
}
|
|
|
|
private var bRefreshErrorWillShown = false
|
|
|
|
private fun hideRefreshError() {
|
|
if(! bRefreshErrorWillShown) return
|
|
bRefreshErrorWillShown = false
|
|
if(llRefreshError.visibility == View.GONE) return
|
|
val aa = AlphaAnimation(1f, 0f)
|
|
aa.duration = 666L
|
|
aa.setAnimationListener(object : Animation.AnimationListener {
|
|
override fun onAnimationRepeat(animation : Animation?) {
|
|
}
|
|
|
|
override fun onAnimationStart(animation : Animation?) {
|
|
}
|
|
|
|
override fun onAnimationEnd(animation : Animation?) {
|
|
if(! bRefreshErrorWillShown) llRefreshError.visibility = View.GONE
|
|
}
|
|
})
|
|
llRefreshError.clearAnimation()
|
|
llRefreshError.startAnimation(aa)
|
|
}
|
|
|
|
private fun showRefreshError() {
|
|
val column = column
|
|
if(column == null) {
|
|
hideRefreshError()
|
|
return
|
|
}
|
|
|
|
val refreshError = column.mRefreshLoadingError
|
|
// val refreshErrorTime = column.mRefreshLoadingErrorTime
|
|
if(refreshError.isEmpty()) {
|
|
hideRefreshError()
|
|
return
|
|
}
|
|
|
|
tvRefreshError.text = refreshError
|
|
when(column.mRefreshLoadingErrorPopupState) {
|
|
// initially expanded
|
|
0 -> {
|
|
tvRefreshError.isSingleLine = false
|
|
tvRefreshError.ellipsize = null
|
|
}
|
|
|
|
// tap to minimize
|
|
1 -> {
|
|
tvRefreshError.isSingleLine = true
|
|
tvRefreshError.ellipsize = TextUtils.TruncateAt.END
|
|
}
|
|
}
|
|
|
|
if(! bRefreshErrorWillShown) {
|
|
bRefreshErrorWillShown = true
|
|
if(llRefreshError.visibility == View.GONE) {
|
|
llRefreshError.visibility = View.VISIBLE
|
|
val aa = AlphaAnimation(0f, 1f)
|
|
aa.duration = 666L
|
|
llRefreshError.clearAnimation()
|
|
llRefreshError.startAnimation(aa)
|
|
}
|
|
}
|
|
}
|
|
|
|
fun saveScrollPosition() : Boolean {
|
|
val column = this.column
|
|
when {
|
|
column == null -> log.d("saveScrollPosition [%d] , column==null", page_idx)
|
|
|
|
column.is_dispose.get() -> log.d(
|
|
"saveScrollPosition [%d] , column is disposed",
|
|
page_idx
|
|
)
|
|
|
|
listView.visibility != View.VISIBLE -> {
|
|
val scroll_save = ScrollPosition()
|
|
column.scroll_save = scroll_save
|
|
log.d(
|
|
"saveScrollPosition [%d] %s , listView is not visible, save %s,%s"
|
|
, page_idx
|
|
, column.getColumnName(true)
|
|
, scroll_save.adapterIndex
|
|
, scroll_save.offset
|
|
)
|
|
return true
|
|
}
|
|
|
|
else -> {
|
|
val scroll_save = ScrollPosition(this)
|
|
column.scroll_save = scroll_save
|
|
log.d(
|
|
"saveScrollPosition [%d] %s , listView is visible, save %s,%s"
|
|
, page_idx
|
|
, column.getColumnName(true)
|
|
, scroll_save.adapterIndex
|
|
, scroll_save.offset
|
|
)
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
fun setScrollPosition(sp : ScrollPosition, deltaDp : Float = 0f) {
|
|
val last_adapter = listView.adapter
|
|
if(column == null || last_adapter == null) return
|
|
|
|
sp.restore(this)
|
|
|
|
// 復元した後に意図的に少し上下にずらしたい
|
|
val dy = (deltaDp * activity.density + 0.5f).toInt()
|
|
if(dy != 0) listView.postDelayed(Runnable {
|
|
if(column == null || listView.adapter !== last_adapter) return@Runnable
|
|
|
|
try {
|
|
val recycler = fieldRecycler.get(listView) as RecyclerView.Recycler
|
|
val state = fieldState.get(listView) as RecyclerView.State
|
|
listLayoutManager.scrollVerticallyBy(dy, recycler, state)
|
|
} catch(ex : Throwable) {
|
|
log.trace(ex)
|
|
log.e("can't access field in class %s", RecyclerView::class.java.simpleName)
|
|
}
|
|
}, 20L)
|
|
}
|
|
|
|
internal inner class AdapterItemHeightWorkarea internal constructor(val adapter : ItemListAdapter) :
|
|
Closeable {
|
|
|
|
private val item_width : Int
|
|
private val widthSpec : Int
|
|
private var lastViewType : Int = - 1
|
|
private var lastViewHolder : RecyclerView.ViewHolder? = null
|
|
|
|
init {
|
|
this.item_width = listView.width - listView.paddingLeft - listView.paddingRight
|
|
this.widthSpec = View.MeasureSpec.makeMeasureSpec(item_width, View.MeasureSpec.EXACTLY)
|
|
}
|
|
|
|
override fun close() {
|
|
val childViewHolder = lastViewHolder
|
|
if(childViewHolder != null) {
|
|
adapter.onViewRecycled(childViewHolder)
|
|
lastViewHolder = null
|
|
}
|
|
}
|
|
|
|
// この関数はAdapterViewの項目の(marginを含む)高さを返す
|
|
fun getAdapterItemHeight(adapterIndex : Int) : Int {
|
|
|
|
fun View.getTotalHeight() : Int {
|
|
measure(widthSpec, heightSpec)
|
|
val lp = layoutParams as? ViewGroup.MarginLayoutParams
|
|
return measuredHeight + (lp?.topMargin ?: 0) + (lp?.bottomMargin ?: 0)
|
|
}
|
|
|
|
listView.findViewHolderForAdapterPosition(adapterIndex)?.itemView?.let {
|
|
return it.getTotalHeight()
|
|
}
|
|
|
|
log.d("getAdapterItemHeight idx=$adapterIndex createView")
|
|
|
|
val viewType = adapter.getItemViewType(adapterIndex)
|
|
|
|
var childViewHolder = lastViewHolder
|
|
if(childViewHolder == null || lastViewType != viewType) {
|
|
if(childViewHolder != null) {
|
|
adapter.onViewRecycled(childViewHolder)
|
|
}
|
|
childViewHolder = adapter.onCreateViewHolder(listView, viewType)
|
|
lastViewHolder = childViewHolder
|
|
lastViewType = viewType
|
|
}
|
|
adapter.onBindViewHolder(childViewHolder, adapterIndex)
|
|
return childViewHolder.itemView.getTotalHeight()
|
|
}
|
|
}
|
|
|
|
// 特定の要素が特定の位置に来るようにスクロール位置を調整する
|
|
fun setListItemTop(listIndex : Int, yArg : Int) {
|
|
var adapterIndex = column?.toAdapterIndex(listIndex) ?: return
|
|
|
|
val adapter = status_adapter
|
|
if(adapter == null) {
|
|
log.e("setListItemTop: missing status adapter")
|
|
return
|
|
}
|
|
|
|
var y = yArg
|
|
AdapterItemHeightWorkarea(adapter).use { workarea ->
|
|
while(y > 0 && adapterIndex > 0) {
|
|
-- adapterIndex
|
|
y -= workarea.getAdapterItemHeight(adapterIndex)
|
|
y -= ListDivider.height
|
|
}
|
|
}
|
|
|
|
if(adapterIndex == 0 && y > 0) y = 0
|
|
listLayoutManager.scrollToPositionWithOffset(adapterIndex, y)
|
|
}
|
|
|
|
// この関数は scrollToPositionWithOffset 用のオフセットを返す
|
|
fun getListItemOffset(listIndex : Int) : Int {
|
|
|
|
val adapterIndex = column?.toAdapterIndex(listIndex)
|
|
?: return 0
|
|
|
|
val childView = listLayoutManager.findViewByPosition(adapterIndex)
|
|
?: throw IndexOutOfBoundsException("findViewByPosition($adapterIndex) returns null.")
|
|
|
|
// スクロールとともにtopは減少する
|
|
// しかしtopMarginがあるので最大値は4である
|
|
// この関数は scrollToPositionWithOffset 用のオフセットを返すので top - topMargin を返す
|
|
return childView.top - ((childView.layoutParams as? ViewGroup.MarginLayoutParams)?.topMargin
|
|
?: 0)
|
|
}
|
|
|
|
fun findFirstVisibleListItem() : Int {
|
|
|
|
val adapterIndex = listLayoutManager.findFirstVisibleItemPosition()
|
|
|
|
if(adapterIndex == RecyclerView.NO_POSITION)
|
|
throw IndexOutOfBoundsException()
|
|
|
|
return column?.toListIndex(adapterIndex)
|
|
?: throw IndexOutOfBoundsException()
|
|
|
|
}
|
|
|
|
fun scrollToTop() {
|
|
try {
|
|
listView.stopScroll()
|
|
} catch(ex : Throwable) {
|
|
log.e(ex, "stopScroll failed.")
|
|
}
|
|
try {
|
|
listLayoutManager.scrollToPositionWithOffset(0, 0)
|
|
} catch(ex : Throwable) {
|
|
log.e(ex, "scrollToPositionWithOffset failed.")
|
|
}
|
|
}
|
|
|
|
fun scrollToTop2() {
|
|
val status_adapter = this.status_adapter
|
|
if(binding_busy || status_adapter == null) return
|
|
if(status_adapter.itemCount > 0) {
|
|
scrollToTop()
|
|
}
|
|
}
|
|
|
|
private fun clickQuickFilter(filter : Int) {
|
|
column?.quick_filter = filter
|
|
showQuickFilter()
|
|
activity.app_state.saveColumnList()
|
|
column?.startLoading()
|
|
}
|
|
|
|
private fun showQuickFilter() {
|
|
val column = this.column ?: return
|
|
|
|
svQuickFilter.vg(column.isNotificationColumn) ?: return
|
|
|
|
btnQuickFilterReaction.vg(column.isMisskey)
|
|
btnQuickFilterFavourite.vg(! column.isMisskey)
|
|
|
|
val insideColumnSetting = Pref.bpMoveNotificationsQuickFilter(activity.pref)
|
|
|
|
val showQuickFilterButton : (btn : View, iconId : Int, selected : Boolean) -> Unit
|
|
|
|
if(insideColumnSetting) {
|
|
svQuickFilter.setBackgroundColor(0)
|
|
|
|
val colorFg = getAttributeColor(activity, R.attr.colorContentText)
|
|
val colorBgSelected = colorFg.applyAlphaMultiplier(0.25f)
|
|
val colorFgList = ColorStateList.valueOf(colorFg)
|
|
val colorBg = getAttributeColor(activity, R.attr.colorColumnSettingBackground)
|
|
showQuickFilterButton = { btn, iconId, selected ->
|
|
btn.backgroundDrawable =
|
|
getAdaptiveRippleDrawableRound(
|
|
activity,
|
|
if(selected) colorBgSelected else colorBg,
|
|
colorFg,
|
|
roundNormal = true
|
|
)
|
|
|
|
when(btn) {
|
|
is TextView -> btn.textColor = colorFg
|
|
|
|
is ImageButton -> {
|
|
btn.setImageResource(iconId)
|
|
btn.imageTintList = colorFgList
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
val colorBg = column.getHeaderBackgroundColor()
|
|
val colorFg = column.getHeaderNameColor()
|
|
val colorFgList = ColorStateList.valueOf(colorFg)
|
|
val colorBgSelected = Color.rgb(
|
|
(Color.red(colorBg) * 3 + Color.red(colorFg)) / 4,
|
|
(Color.green(colorBg) * 3 + Color.green(colorFg)) / 4,
|
|
(Color.blue(colorBg) * 3 + Color.blue(colorFg)) / 4
|
|
)
|
|
svQuickFilter.setBackgroundColor(colorBg)
|
|
|
|
showQuickFilterButton = { btn, iconId, selected ->
|
|
|
|
btn.backgroundDrawable = getAdaptiveRippleDrawableRound(
|
|
activity,
|
|
if(selected) colorBgSelected else colorBg,
|
|
colorFg
|
|
)
|
|
|
|
when(btn) {
|
|
is TextView -> btn.textColor = colorFg
|
|
|
|
is ImageButton -> {
|
|
btn.setImageResource(iconId)
|
|
btn.imageTintList = colorFgList
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
showQuickFilterButton(
|
|
btnQuickFilterAll,
|
|
0,
|
|
column.quick_filter == Column.QUICK_FILTER_ALL
|
|
)
|
|
|
|
showQuickFilterButton(
|
|
btnQuickFilterMention,
|
|
R.drawable.ic_reply,
|
|
column.quick_filter == Column.QUICK_FILTER_MENTION
|
|
)
|
|
|
|
showQuickFilterButton(
|
|
btnQuickFilterFavourite,
|
|
R.drawable.ic_star,
|
|
column.quick_filter == Column.QUICK_FILTER_FAVOURITE
|
|
)
|
|
|
|
showQuickFilterButton(
|
|
btnQuickFilterBoost,
|
|
R.drawable.ic_repeat,
|
|
column.quick_filter == Column.QUICK_FILTER_BOOST
|
|
)
|
|
|
|
showQuickFilterButton(
|
|
btnQuickFilterFollow,
|
|
R.drawable.ic_follow_plus,
|
|
column.quick_filter == Column.QUICK_FILTER_FOLLOW
|
|
)
|
|
|
|
showQuickFilterButton(
|
|
btnQuickFilterPost,
|
|
R.drawable.ic_send,
|
|
column.quick_filter == Column.QUICK_FILTER_POST
|
|
)
|
|
|
|
showQuickFilterButton(
|
|
btnQuickFilterReaction,
|
|
R.drawable.ic_add,
|
|
column.quick_filter == Column.QUICK_FILTER_REACTION
|
|
)
|
|
|
|
showQuickFilterButton(
|
|
btnQuickFilterVote,
|
|
R.drawable.ic_vote,
|
|
column.quick_filter == Column.QUICK_FILTER_VOTE
|
|
)
|
|
}
|
|
|
|
private fun inflate(activity : ActMain, parent : ViewGroup) = with(activity.UI {}) {
|
|
val b = Benchmark(log, "Item-Inflate", 40L)
|
|
var label : TextView? = null
|
|
val rv = verticalLayout {
|
|
// トップレベルのViewGroupのlparamsはイニシャライザ内部に置くしかないみたい
|
|
val lp = parent.generateLayoutParamsEx()
|
|
if(lp != null) {
|
|
lp.width = matchParent
|
|
lp.height = matchParent
|
|
if(lp is ViewGroup.MarginLayoutParams) {
|
|
lp.setMargins(0, 0, 0, 0)
|
|
}
|
|
layoutParams = lp
|
|
}
|
|
|
|
llColumnHeader = customView<OutsideDrawerLayout> {
|
|
lparams(matchParent, wrapContent)
|
|
|
|
orientation = LinearLayout.VERTICAL
|
|
|
|
background = ContextCompat.getDrawable(context, R.drawable.bg_column_header)
|
|
startPadding = dip(12)
|
|
endPadding = dip(12)
|
|
topPadding = dip(3)
|
|
bottomPadding = dip(3)
|
|
|
|
linearLayout {
|
|
lparams(matchParent, wrapContent)
|
|
gravity = Gravity.BOTTOM
|
|
|
|
tvColumnContext = textView {
|
|
gravity = Gravity.END
|
|
startPadding = dip(4)
|
|
endPadding = dip(4)
|
|
textColor = getAttributeColor(context, R.attr.colorColumnHeaderAcct)
|
|
textSize = 12f
|
|
|
|
}.lparams(0, wrapContent) {
|
|
weight = 1f
|
|
}
|
|
|
|
tvColumnStatus = textView {
|
|
gravity = Gravity.END
|
|
textColor = getAttributeColor(context, R.attr.colorColumnHeaderPageNumber)
|
|
textSize = 12f
|
|
|
|
}.lparams(wrapContent, wrapContent) {
|
|
marginStart = dip(12)
|
|
}
|
|
|
|
tvColumnIndex = textView {
|
|
gravity = Gravity.END
|
|
textColor = getAttributeColor(context, R.attr.colorColumnHeaderPageNumber)
|
|
textSize = 12f
|
|
|
|
}.lparams(wrapContent, wrapContent) {
|
|
marginStart = dip(4)
|
|
}
|
|
}
|
|
|
|
linearLayout {
|
|
lparams(matchParent, wrapContent) {
|
|
topMargin = dip(0)
|
|
}
|
|
gravity = Gravity.CENTER_VERTICAL
|
|
isBaselineAligned = false
|
|
|
|
ivColumnIcon = imageView {
|
|
|
|
importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
|
|
scaleType = ImageView.ScaleType.FIT_CENTER
|
|
|
|
}.lparams(dip(32), dip(32)) {
|
|
endMargin = dip(4)
|
|
}
|
|
|
|
tvColumnName = textView {
|
|
// Kannada語の "ಸ್ಥಳೀಯ ಟೈಮ್ ಲೈನ್" の上下が途切れることがあるらしい
|
|
// GS10+では再現しなかった
|
|
}.lparams(dip(0), wrapContent) {
|
|
weight = 1f
|
|
}
|
|
|
|
frameLayout {
|
|
lparams(wrapContent, wrapContent) {
|
|
gravity = Gravity.CENTER_VERTICAL
|
|
startMargin = dip(2)
|
|
}
|
|
clipChildren = false
|
|
|
|
btnAnnouncements = imageButton {
|
|
background =
|
|
ContextCompat.getDrawable(
|
|
context,
|
|
R.drawable.btn_bg_transparent_round6dp
|
|
)
|
|
contentDescription = context.getString(R.string.announcements)
|
|
setImageResource(R.drawable.ic_info_outline)
|
|
padding = dip(8)
|
|
scaleType = ImageView.ScaleType.FIT_CENTER
|
|
|
|
btnAnnouncementsCutout = Paint().apply {
|
|
isAntiAlias = true
|
|
}
|
|
val path = Path()
|
|
addOutsideDrawer(this) { canvas, parent, view, left, top ->
|
|
if(llAnnouncementsBox.visibility == View.VISIBLE) {
|
|
val viewW = view.width
|
|
val viewH = view.height
|
|
val triTopX = (left + viewW / 2).toFloat()
|
|
val triTopY = top.toFloat() + viewH * 0.75f
|
|
val triBottomLeft = left.toFloat()
|
|
val triBottomRight = (left + viewW).toFloat()
|
|
val triBottom = parent.height.toFloat()
|
|
path.reset()
|
|
path.moveTo(triTopX, triTopY)
|
|
path.lineTo(triBottomRight, triBottom)
|
|
path.lineTo(triBottomLeft, triBottom)
|
|
path.lineTo(triTopX, triTopY)
|
|
canvas.drawPath(path, btnAnnouncementsCutout)
|
|
}
|
|
}
|
|
}.lparams(dip(40), dip(40))
|
|
|
|
btnAnnouncementsBadge = imageView {
|
|
setImageResource(R.drawable.announcements_dot)
|
|
scaleType = ImageView.ScaleType.FIT_CENTER
|
|
}.lparams(dip(7), dip(7)) {
|
|
gravity = Gravity.END or Gravity.TOP
|
|
endMargin = dip(4)
|
|
topMargin = dip(4)
|
|
}
|
|
|
|
}
|
|
|
|
frameLayout {
|
|
lparams(wrapContent, wrapContent) {
|
|
gravity = Gravity.CENTER_VERTICAL
|
|
startMargin = dip(2)
|
|
}
|
|
clipChildren = false
|
|
|
|
btnColumnSetting = imageButton {
|
|
background =
|
|
ContextCompat.getDrawable(
|
|
context,
|
|
R.drawable.btn_bg_transparent_round6dp
|
|
)
|
|
contentDescription = context.getString(R.string.setting)
|
|
setImageResource(R.drawable.ic_tune)
|
|
padding = dip(8)
|
|
scaleType = ImageView.ScaleType.FIT_CENTER
|
|
|
|
val paint = Paint().apply {
|
|
isAntiAlias = true
|
|
color =
|
|
getAttributeColor(context, R.attr.colorColumnSettingBackground)
|
|
}
|
|
val path = Path()
|
|
addOutsideDrawer(this) { canvas, parent, view, left, top ->
|
|
if(llColumnSetting.visibility == View.VISIBLE) {
|
|
val viewW = view.width
|
|
val viewH = view.height
|
|
val triTopX = (left + viewW / 2).toFloat()
|
|
val triTopY = top.toFloat() + viewH * 0.75f
|
|
val triBottomLeft = left.toFloat()
|
|
val triBottomRight = (left + viewW).toFloat()
|
|
val triBottom = parent.height.toFloat()
|
|
path.reset()
|
|
path.moveTo(triTopX, triTopY)
|
|
path.lineTo(triBottomRight, triBottom)
|
|
path.lineTo(triBottomLeft, triBottom)
|
|
path.lineTo(triTopX, triTopY)
|
|
canvas.drawPath(path, paint)
|
|
}
|
|
}
|
|
}.lparams(dip(40), dip(40))
|
|
|
|
}
|
|
|
|
btnColumnReload = imageButton {
|
|
background =
|
|
ContextCompat.getDrawable(
|
|
context,
|
|
R.drawable.btn_bg_transparent_round6dp
|
|
)
|
|
contentDescription = context.getString(R.string.reload)
|
|
setImageResource(R.drawable.ic_refresh)
|
|
padding = dip(8)
|
|
scaleType = ImageView.ScaleType.FIT_CENTER
|
|
|
|
}.lparams(dip(40), dip(40)) {
|
|
gravity = Gravity.CENTER_VERTICAL
|
|
startMargin = dip(2)
|
|
|
|
}
|
|
|
|
btnColumnClose = imageButton {
|
|
background =
|
|
ContextCompat.getDrawable(
|
|
context,
|
|
R.drawable.btn_bg_transparent_round6dp
|
|
)
|
|
contentDescription = context.getString(R.string.close_column)
|
|
setImageResource(R.drawable.ic_close)
|
|
padding = dip(8)
|
|
scaleType = ImageView.ScaleType.FIT_CENTER
|
|
|
|
}.lparams(dip(40), dip(40)) {
|
|
gravity = Gravity.CENTER_VERTICAL
|
|
startMargin = dip(2)
|
|
|
|
}
|
|
}
|
|
} // end of column header
|
|
|
|
llColumnSetting = maxHeightScrollView {
|
|
lparams(matchParent, wrapContent)
|
|
isScrollbarFadingEnabled = false
|
|
maxHeight = dip(240)
|
|
|
|
backgroundColor =
|
|
getAttributeColor(context, R.attr.colorColumnSettingBackground)
|
|
|
|
llColumnSettingInside = verticalLayout {
|
|
lparams(matchParent, wrapContent)
|
|
|
|
startPadding = dip(12)
|
|
endPadding = dip(12)
|
|
topPadding = dip(3)
|
|
bottomPadding = dip(3)
|
|
|
|
llHashtagExtra = verticalLayout {
|
|
lparams(matchParent, wrapContent)
|
|
|
|
label = textView {
|
|
textColor =
|
|
getAttributeColor(context, R.attr.colorColumnHeaderPageNumber)
|
|
text = context.getString(R.string.hashtag_extra_any)
|
|
}.lparams(matchParent, wrapContent)
|
|
|
|
etHashtagExtraAny = editText {
|
|
id = View.generateViewId()
|
|
inputType = InputType.TYPE_CLASS_TEXT
|
|
maxLines = 1
|
|
setHorizontallyScrolling(true)
|
|
isHorizontalScrollBarEnabled = true
|
|
}.lparams(matchParent, wrapContent)
|
|
label?.labelFor = etHashtagExtraAny.id
|
|
|
|
label = textView {
|
|
textColor =
|
|
getAttributeColor(context, R.attr.colorColumnHeaderPageNumber)
|
|
text = context.getString(R.string.hashtag_extra_all)
|
|
}.lparams(matchParent, wrapContent)
|
|
|
|
etHashtagExtraAll = editText {
|
|
id = View.generateViewId()
|
|
inputType = InputType.TYPE_CLASS_TEXT
|
|
maxLines = 1
|
|
setHorizontallyScrolling(true)
|
|
isHorizontalScrollBarEnabled = true
|
|
}.lparams(matchParent, wrapContent)
|
|
label?.labelFor = etHashtagExtraAll.id
|
|
|
|
label = textView {
|
|
textColor =
|
|
getAttributeColor(context, R.attr.colorColumnHeaderPageNumber)
|
|
text = context.getString(R.string.hashtag_extra_none)
|
|
}.lparams(matchParent, wrapContent)
|
|
|
|
etHashtagExtraNone = editText {
|
|
id = View.generateViewId()
|
|
inputType = InputType.TYPE_CLASS_TEXT
|
|
maxLines = 1
|
|
setHorizontallyScrolling(true)
|
|
isHorizontalScrollBarEnabled = true
|
|
}.lparams(matchParent, wrapContent)
|
|
label?.labelFor = etHashtagExtraNone.id
|
|
} // end of hashtag extra
|
|
|
|
cbDontCloseColumn = checkBox {
|
|
text = context.getString(R.string.dont_close_column)
|
|
}.lparams(matchParent, wrapContent)
|
|
|
|
cbRemoteOnly = checkBox {
|
|
text = context.getString(R.string.remote_only)
|
|
}.lparams(matchParent, wrapContent)
|
|
|
|
cbWithAttachment = checkBox {
|
|
text = context.getString(R.string.with_attachment)
|
|
}.lparams(matchParent, wrapContent)
|
|
|
|
cbWithHighlight = checkBox {
|
|
text = context.getString(R.string.with_highlight)
|
|
}.lparams(matchParent, wrapContent)
|
|
|
|
cbDontShowBoost = checkBox {
|
|
text = context.getString(R.string.dont_show_boost)
|
|
}.lparams(matchParent, wrapContent)
|
|
|
|
cbDontShowFavourite = checkBox {
|
|
text = context.getString(R.string.dont_show_favourite)
|
|
}.lparams(matchParent, wrapContent)
|
|
|
|
cbDontShowFollow = checkBox {
|
|
text = context.getString(R.string.dont_show_follow)
|
|
}.lparams(matchParent, wrapContent)
|
|
|
|
cbDontShowReply = checkBox {
|
|
text = context.getString(R.string.dont_show_reply)
|
|
}.lparams(matchParent, wrapContent)
|
|
|
|
cbDontShowReaction = checkBox {
|
|
text = context.getString(R.string.dont_show_reaction)
|
|
}.lparams(matchParent, wrapContent)
|
|
|
|
cbDontShowVote = checkBox {
|
|
text = context.getString(R.string.dont_show_vote)
|
|
}.lparams(matchParent, wrapContent)
|
|
|
|
cbDontShowNormalToot = checkBox {
|
|
text = context.getString(R.string.dont_show_normal_toot)
|
|
}.lparams(matchParent, wrapContent)
|
|
|
|
cbDontShowNonPublicToot = checkBox {
|
|
text = context.getString(R.string.dont_show_non_public_toot)
|
|
}.lparams(matchParent, wrapContent)
|
|
|
|
cbInstanceLocal = checkBox {
|
|
text = context.getString(R.string.instance_local)
|
|
}.lparams(matchParent, wrapContent)
|
|
|
|
cbDontStreaming = checkBox {
|
|
text = context.getString(R.string.dont_use_streaming_api)
|
|
}.lparams(matchParent, wrapContent)
|
|
|
|
cbDontAutoRefresh = checkBox {
|
|
text = context.getString(R.string.dont_refresh_on_activity_resume)
|
|
}.lparams(matchParent, wrapContent)
|
|
|
|
cbHideMediaDefault = checkBox {
|
|
text = context.getString(R.string.hide_media_default)
|
|
}.lparams(matchParent, wrapContent)
|
|
|
|
cbSystemNotificationNotRelated = checkBox {
|
|
text = context.getString(R.string.system_notification_not_related)
|
|
}.lparams(matchParent, wrapContent)
|
|
|
|
cbEnableSpeech = checkBox {
|
|
text = context.getString(R.string.enable_speech)
|
|
}.lparams(matchParent, wrapContent)
|
|
|
|
cbOldApi = checkBox {
|
|
text = context.getString(R.string.use_old_api)
|
|
}.lparams(matchParent, wrapContent)
|
|
|
|
llRegexFilter = linearLayout {
|
|
lparams(matchParent, wrapContent)
|
|
|
|
label = textView {
|
|
textColor =
|
|
getAttributeColor(context, R.attr.colorColumnHeaderPageNumber)
|
|
text = context.getString(R.string.regex_filter)
|
|
}.lparams(wrapContent, wrapContent)
|
|
|
|
tvRegexFilterError = textView {
|
|
textColor = getAttributeColor(context, R.attr.colorRegexFilterError)
|
|
}.lparams(0, wrapContent) {
|
|
weight = 1f
|
|
startMargin = dip(4)
|
|
}
|
|
|
|
}
|
|
etRegexFilter = editText {
|
|
id = View.generateViewId()
|
|
inputType = InputType.TYPE_CLASS_TEXT
|
|
maxLines = 1
|
|
setHorizontallyScrolling(true)
|
|
isHorizontalScrollBarEnabled = true
|
|
|
|
}.lparams(matchParent, wrapContent)
|
|
label?.labelFor = etRegexFilter.id
|
|
|
|
btnDeleteNotification = button {
|
|
isAllCaps = false
|
|
text = context.getString(R.string.notification_delete)
|
|
|
|
}.lparams(matchParent, wrapContent)
|
|
|
|
btnColor = button {
|
|
isAllCaps = false
|
|
text = context.getString(R.string.color_and_background)
|
|
}.lparams(matchParent, wrapContent)
|
|
|
|
btnLanguageFilter = button {
|
|
isAllCaps = false
|
|
text = context.getString(R.string.language_filter)
|
|
}.lparams(matchParent, wrapContent)
|
|
|
|
}
|
|
|
|
} // end of column setting scroll view
|
|
|
|
llAnnouncementsBox = verticalLayout {
|
|
lparams(matchParent, wrapContent) {
|
|
startMargin = dip(6)
|
|
endMargin = dip(6)
|
|
bottomMargin = dip(2)
|
|
}
|
|
|
|
val buttonHeight = ActMain.boostButtonSize
|
|
val paddingH = (buttonHeight.toFloat() * 0.1f + 0.5f).toInt()
|
|
val paddingV = (buttonHeight.toFloat() * 0.1f + 0.5f).toInt()
|
|
|
|
linearLayout {
|
|
lparams(matchParent, wrapContent)
|
|
val pad_lr = dip(6)
|
|
setPadding(pad_lr, 0, pad_lr, 0)
|
|
|
|
background = ContextCompat.getDrawable(
|
|
context,
|
|
R.drawable.btn_bg_transparent_round6dp
|
|
)
|
|
|
|
gravity = Gravity.CENTER_VERTICAL or Gravity.END
|
|
|
|
tvAnnouncementsCaption = textView {
|
|
gravity = Gravity.END or Gravity.CENTER_VERTICAL
|
|
text = context.getString(R.string.announcements)
|
|
}.lparams(0, wrapContent) {
|
|
weight = 1f
|
|
}
|
|
|
|
btnAnnouncementsPrev = imageButton {
|
|
|
|
background = ContextCompat.getDrawable(
|
|
context,
|
|
R.drawable.btn_bg_transparent_round6dp
|
|
)
|
|
contentDescription = context.getString(R.string.previous)
|
|
imageResource = R.drawable.ic_arrow_start
|
|
setPadding(paddingH, paddingV, paddingH, paddingV)
|
|
scaleType = ImageView.ScaleType.FIT_CENTER
|
|
|
|
}.lparams(buttonHeight, buttonHeight) {
|
|
marginStart = dip(4)
|
|
}
|
|
|
|
tvAnnouncementsIndex = textView {
|
|
}.lparams(wrapContent, wrapContent) {
|
|
marginStart = dip(4)
|
|
}
|
|
|
|
btnAnnouncementsNext = imageButton {
|
|
|
|
background = ContextCompat.getDrawable(
|
|
context,
|
|
R.drawable.btn_bg_transparent_round6dp
|
|
)
|
|
contentDescription = context.getString(R.string.next)
|
|
imageResource = R.drawable.ic_arrow_end
|
|
setPadding(paddingH, paddingV, paddingH, paddingV)
|
|
scaleType = ImageView.ScaleType.FIT_CENTER
|
|
|
|
}.lparams(buttonHeight, buttonHeight) {
|
|
marginStart = dip(4)
|
|
}
|
|
}
|
|
|
|
llAnnouncements = maxHeightScrollView {
|
|
lparams(matchParent, wrapContent) {
|
|
topMargin = dip(1)
|
|
}
|
|
|
|
val pad_lr = dip(6)
|
|
val pad_tb = dip(2)
|
|
setPadding(pad_lr, pad_tb, pad_lr, pad_tb)
|
|
|
|
scrollBarStyle = View.SCROLLBARS_OUTSIDE_OVERLAY
|
|
isScrollbarFadingEnabled = false
|
|
|
|
maxHeight = dip(240)
|
|
|
|
verticalLayout {
|
|
lparams(matchParent, wrapContent)
|
|
|
|
// 期間があれば表示する
|
|
tvAnnouncementPeriod = textView {
|
|
gravity = Gravity.END
|
|
}.lparams(matchParent, wrapContent) {
|
|
bottomMargin = dip(3)
|
|
}
|
|
|
|
tvAnnouncementContent = myTextView {
|
|
setLineSpacing(lineSpacingExtra, 1.1f)
|
|
// tools:text="Contents\nContents"
|
|
}.lparams(matchParent, wrapContent) {
|
|
topMargin = dip(3)
|
|
}
|
|
|
|
llAnnouncementExtra = verticalLayout {
|
|
lparams(matchParent, wrapContent) {
|
|
topMargin = dip(3)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
llSearch = verticalLayout {
|
|
lparams(matchParent, wrapContent)
|
|
|
|
linearLayout {
|
|
lparams(matchParent, wrapContent)
|
|
isBaselineAligned = false
|
|
gravity = Gravity.CENTER
|
|
|
|
etSearch = editText {
|
|
id = View.generateViewId()
|
|
imeOptions = EditorInfo.IME_ACTION_SEARCH
|
|
inputType = InputType.TYPE_CLASS_TEXT
|
|
maxLines = 1
|
|
|
|
}.lparams(0, wrapContent) {
|
|
weight = 1f
|
|
}
|
|
|
|
btnSearchClear = imageButton {
|
|
backgroundResource = R.drawable.btn_bg_transparent_round6dp
|
|
contentDescription = context.getString(R.string.clear)
|
|
imageResource = R.drawable.ic_close
|
|
imageTintList = ColorStateList.valueOf(
|
|
getAttributeColor(
|
|
context,
|
|
R.attr.colorVectorDrawable
|
|
)
|
|
)
|
|
}.lparams(dip(40), dip(40)) {
|
|
startMargin = dip(4)
|
|
}
|
|
|
|
btnSearch = imageButton {
|
|
backgroundResource = R.drawable.btn_bg_transparent_round6dp
|
|
contentDescription = context.getString(R.string.search)
|
|
imageResource = R.drawable.ic_search
|
|
imageTintList = ColorStateList.valueOf(
|
|
getAttributeColor(
|
|
context,
|
|
R.attr.colorVectorDrawable
|
|
)
|
|
)
|
|
}.lparams(dip(40), dip(40)) {
|
|
startMargin = dip(4)
|
|
}
|
|
}
|
|
|
|
cbResolve = checkBox {
|
|
text = context.getString(R.string.resolve_non_local_account)
|
|
}.lparams(wrapContent, wrapContent) // チェックボックスの余白はタッチ判定外
|
|
|
|
} // end of search bar
|
|
|
|
llListList = linearLayout {
|
|
lparams(matchParent, wrapContent)
|
|
|
|
isBaselineAligned = false
|
|
gravity = Gravity.CENTER
|
|
|
|
etListName = editText {
|
|
hint = context.getString(R.string.list_create_hint)
|
|
imeOptions = EditorInfo.IME_ACTION_SEND
|
|
inputType = InputType.TYPE_CLASS_TEXT
|
|
}.lparams(0, wrapContent) {
|
|
weight = 1f
|
|
}
|
|
|
|
btnListAdd = imageButton {
|
|
backgroundResource = R.drawable.btn_bg_transparent_round6dp
|
|
contentDescription = context.getString(R.string.add)
|
|
imageResource = R.drawable.ic_add
|
|
imageTintList = ColorStateList.valueOf(
|
|
getAttributeColor(
|
|
context,
|
|
R.attr.colorVectorDrawable
|
|
)
|
|
)
|
|
}.lparams(dip(40), dip(40)) {
|
|
startMargin = dip(4)
|
|
}
|
|
} // end of list list header
|
|
|
|
svQuickFilter = horizontalScrollView {
|
|
lparams(matchParent, wrapContent)
|
|
isFillViewport = true
|
|
linearLayout {
|
|
lparams(matchParent, dip(40))
|
|
|
|
btnQuickFilterAll = button {
|
|
backgroundResource = R.drawable.btn_bg_transparent_round6dp
|
|
minWidthCompat = dip(40)
|
|
startPadding = dip(4)
|
|
endPadding = dip(4)
|
|
isAllCaps = false
|
|
stateListAnimator = null
|
|
text = context.getString(R.string.all)
|
|
}.lparams(wrapContent, matchParent) {
|
|
margin = 0
|
|
}
|
|
|
|
btnQuickFilterMention = imageButton {
|
|
backgroundResource = R.drawable.btn_bg_transparent_round6dp
|
|
contentDescription = context.getString(R.string.mention2)
|
|
}.lparams(dip(40), matchParent) {
|
|
margin = 0
|
|
}
|
|
|
|
btnQuickFilterFavourite = imageButton {
|
|
backgroundResource = R.drawable.btn_bg_transparent_round6dp
|
|
contentDescription = context.getString(R.string.favourite)
|
|
}.lparams(dip(40), matchParent) {
|
|
margin = 0
|
|
}
|
|
|
|
btnQuickFilterBoost = imageButton {
|
|
backgroundResource = R.drawable.btn_bg_transparent_round6dp
|
|
contentDescription = context.getString(R.string.boost)
|
|
}.lparams(dip(40), matchParent) {
|
|
margin = 0
|
|
}
|
|
|
|
btnQuickFilterFollow = imageButton {
|
|
backgroundResource = R.drawable.btn_bg_transparent_round6dp
|
|
contentDescription = context.getString(R.string.follow)
|
|
}.lparams(dip(40), matchParent) {
|
|
margin = 0
|
|
}
|
|
|
|
btnQuickFilterPost = imageButton {
|
|
backgroundResource = R.drawable.btn_bg_transparent_round6dp
|
|
contentDescription = context.getString(R.string.notification_type_post)
|
|
}.lparams(dip(40), matchParent) {
|
|
margin = 0
|
|
}
|
|
|
|
btnQuickFilterReaction = imageButton {
|
|
backgroundResource = R.drawable.btn_bg_transparent_round6dp
|
|
contentDescription = context.getString(R.string.reaction)
|
|
}.lparams(dip(40), matchParent) {
|
|
margin = 0
|
|
}
|
|
|
|
|
|
btnQuickFilterVote = imageButton {
|
|
backgroundResource = R.drawable.btn_bg_transparent_round6dp
|
|
contentDescription = context.getString(R.string.vote_polls)
|
|
}.lparams(dip(40), matchParent) {
|
|
margin = 0
|
|
}
|
|
}
|
|
} // end of notification quick filter bar
|
|
|
|
flColumnBackground = frameLayout {
|
|
|
|
ivColumnBackgroundImage = imageView {
|
|
|
|
importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
|
|
scaleType = ImageView.ScaleType.CENTER_CROP
|
|
visibility = View.GONE
|
|
|
|
}.lparams(matchParent, matchParent)
|
|
|
|
tvLoading = textView {
|
|
gravity = Gravity.CENTER
|
|
}.lparams(matchParent, matchParent)
|
|
|
|
refreshLayout = swipyRefreshLayout {
|
|
lparams(matchParent, matchParent)
|
|
|
|
direction = SwipyRefreshLayoutDirection.BOTH
|
|
|
|
// スタイルで指定しないとAndroid 6 で落ちる…
|
|
listView = recyclerView {
|
|
listLayoutManager = LinearLayoutManager(activity)
|
|
layoutManager = listLayoutManager
|
|
|
|
}.lparams(matchParent, matchParent) {
|
|
|
|
}
|
|
}
|
|
|
|
llRefreshError = frameLayout {
|
|
|
|
foregroundGravity = Gravity.BOTTOM
|
|
backgroundResource = R.drawable.bg_refresh_error
|
|
|
|
startPadding = dip(6)
|
|
endPadding = dip(6)
|
|
topPadding = dip(3)
|
|
bottomPadding = dip(3)
|
|
|
|
ivRefreshError = imageView {
|
|
|
|
importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
|
|
scaleType = ImageView.ScaleType.FIT_CENTER
|
|
imageResource = R.drawable.ic_error
|
|
imageTintList = ColorStateList.valueOf(Color.RED)
|
|
|
|
}.lparams(dip(24), dip(24)) {
|
|
gravity = Gravity.START or Gravity.CENTER_VERTICAL
|
|
startMargin = dip(4)
|
|
}
|
|
|
|
tvRefreshError = textView {
|
|
textColor = Color.WHITE
|
|
|
|
}.lparams(matchParent, wrapContent) {
|
|
gravity = Gravity.TOP or Gravity.START
|
|
startMargin = dip(32)
|
|
}
|
|
}.lparams(matchParent, wrapContent) {
|
|
margin = dip(6)
|
|
}
|
|
}.lparams(matchParent, 0) {
|
|
weight = 1f
|
|
}
|
|
}
|
|
b.report()
|
|
rv
|
|
}
|
|
|
|
private fun hideAnnouncements() {
|
|
val column = column ?: return
|
|
|
|
if(column.announcementHideTime <= 0L)
|
|
column.announcementHideTime = System.currentTimeMillis()
|
|
activity.app_state.saveColumnList()
|
|
showAnnouncements()
|
|
}
|
|
|
|
private fun toggleAnnouncements() {
|
|
val column = column ?: return
|
|
|
|
if(llAnnouncementsBox.visibility == View.VISIBLE) {
|
|
if(column.announcementHideTime <= 0L)
|
|
column.announcementHideTime = System.currentTimeMillis()
|
|
} else {
|
|
showColumnSetting(false)
|
|
column.announcementHideTime = 0L
|
|
}
|
|
activity.app_state.saveColumnList()
|
|
showAnnouncements()
|
|
}
|
|
|
|
private fun showAnnouncements(force : Boolean = true) {
|
|
val column = column ?: return
|
|
|
|
if(! force && lastAnnouncementShown >= column.announcementUpdated) {
|
|
return
|
|
}
|
|
lastAnnouncementShown = SystemClock.elapsedRealtime()
|
|
|
|
fun clearExtras() {
|
|
for(invalidator in extra_invalidator_list) {
|
|
invalidator.register(null)
|
|
}
|
|
extra_invalidator_list.clear()
|
|
}
|
|
llAnnouncementExtra.removeAllViews()
|
|
clearExtras()
|
|
|
|
val listShown = TootAnnouncement.filterShown(column.announcements)
|
|
if(listShown?.isEmpty() != false) {
|
|
btnAnnouncements.vg(false)
|
|
llAnnouncementsBox.vg(false)
|
|
btnAnnouncementsBadge.vg(false)
|
|
llColumnHeader.invalidate()
|
|
return
|
|
}
|
|
|
|
btnAnnouncements.vg(true)
|
|
|
|
val expand = column.announcementHideTime <= 0L
|
|
|
|
llAnnouncementsBox.vg(expand)
|
|
llColumnHeader.invalidate()
|
|
|
|
btnAnnouncementsBadge.vg(false)
|
|
if(! expand) {
|
|
val newer = listShown.find { it.updated_at > column.announcementHideTime }
|
|
if(newer != null) {
|
|
column.announcementId = newer.id
|
|
btnAnnouncementsBadge.vg(true)
|
|
}
|
|
return
|
|
}
|
|
|
|
val content_color = column.getContentColor()
|
|
|
|
val item = listShown.find { it.id == column.announcementId }
|
|
?: listShown[0]
|
|
|
|
val itemIndex = listShown.indexOf(item)
|
|
|
|
val enablePaging = listShown.size > 1
|
|
|
|
val alphaPrevNext = if(enablePaging) 1f else 0.3f
|
|
|
|
setIconDrawableId(
|
|
activity,
|
|
btnAnnouncementsPrev,
|
|
R.drawable.ic_arrow_start,
|
|
color = content_color,
|
|
alphaMultiplier = alphaPrevNext
|
|
)
|
|
|
|
setIconDrawableId(
|
|
activity,
|
|
btnAnnouncementsNext,
|
|
R.drawable.ic_arrow_end,
|
|
color = content_color,
|
|
alphaMultiplier = alphaPrevNext
|
|
)
|
|
|
|
|
|
btnAnnouncementsPrev.vg(expand)?.run {
|
|
isEnabled = enablePaging
|
|
}
|
|
btnAnnouncementsNext.vg(expand)?.run {
|
|
isEnabled = enablePaging
|
|
}
|
|
|
|
tvAnnouncementsCaption.textColor = content_color
|
|
tvAnnouncementsIndex.textColor = content_color
|
|
tvAnnouncementPeriod.textColor = content_color
|
|
|
|
val f = activity.timeline_font_size_sp
|
|
if(! f.isNaN()) {
|
|
tvAnnouncementsCaption.textSize = f
|
|
tvAnnouncementsIndex.textSize = f
|
|
tvAnnouncementPeriod.textSize = f
|
|
tvAnnouncementContent.textSize = f
|
|
}
|
|
val spacing = activity.timeline_spacing
|
|
if(spacing != null) {
|
|
tvAnnouncementPeriod.setLineSpacing(0f, spacing)
|
|
tvAnnouncementContent.setLineSpacing(0f, spacing)
|
|
}
|
|
tvAnnouncementsCaption.typeface = ActMain.timeline_font_bold
|
|
val font_normal = ActMain.timeline_font
|
|
tvAnnouncementsIndex.typeface = font_normal
|
|
tvAnnouncementPeriod.typeface = font_normal
|
|
tvAnnouncementContent.typeface = font_normal
|
|
|
|
tvAnnouncementsIndex.vg(expand)?.text =
|
|
activity.getString(R.string.announcements_index, itemIndex + 1, listShown.size)
|
|
llAnnouncements.vg(expand)
|
|
|
|
var periods : StringBuilder? = null
|
|
fun String.appendPeriod() {
|
|
val sb = periods
|
|
if(sb == null) {
|
|
periods = StringBuilder(this)
|
|
} else {
|
|
sb.append("\n")
|
|
sb.append(this)
|
|
}
|
|
}
|
|
|
|
val (strStart, strEnd) = TootStatus.formatTimeRange(
|
|
item.starts_at,
|
|
item.ends_at,
|
|
item.all_day
|
|
)
|
|
|
|
when {
|
|
|
|
// no periods.
|
|
strStart == "" && strEnd == "" -> {
|
|
}
|
|
|
|
// single date
|
|
strStart == strEnd -> {
|
|
activity.getString(R.string.announcements_period1, strStart)
|
|
.appendPeriod()
|
|
}
|
|
|
|
else -> {
|
|
activity.getString(R.string.announcements_period2, strStart, strEnd)
|
|
.appendPeriod()
|
|
}
|
|
}
|
|
|
|
if(item.updated_at > item.published_at) {
|
|
val strUpdateAt = TootStatus.formatTime(activity, item.updated_at, false)
|
|
activity.getString(R.string.edited_at, strUpdateAt).appendPeriod()
|
|
}
|
|
|
|
val sb = periods
|
|
tvAnnouncementPeriod.vg(sb != null)?.text = sb
|
|
|
|
tvAnnouncementContent.textColor = content_color
|
|
tvAnnouncementContent.text = item.decoded_content
|
|
tvAnnouncementContent.tag = this@ColumnViewHolder
|
|
announcementContentInvalidator.register(item.decoded_content)
|
|
|
|
// リアクションの表示
|
|
|
|
val density = activity.density
|
|
|
|
val buttonHeight = ActMain.boostButtonSize
|
|
val marginBetween = (buttonHeight.toFloat() * 0.2f + 0.5f).toInt()
|
|
val marginBottom = (buttonHeight.toFloat() * 0.2f + 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
|
|
).apply {
|
|
bottomMargin = marginBottom
|
|
endMargin = marginBetween
|
|
}
|
|
b.layoutParams = blp
|
|
b.background = ContextCompat.getDrawable(
|
|
activity,
|
|
R.drawable.btn_bg_transparent_round6dp
|
|
)
|
|
|
|
b.contentDescription = activity.getString(R.string.reaction_add)
|
|
b.scaleType = ImageView.ScaleType.FIT_CENTER
|
|
b.padding = paddingV
|
|
b.setOnClickListener {
|
|
addReaction(item, null)
|
|
}
|
|
|
|
setIconDrawableId(
|
|
activity,
|
|
b,
|
|
R.drawable.ic_add,
|
|
color = content_color,
|
|
alphaMultiplier = 1f
|
|
)
|
|
|
|
box.addView(b)
|
|
}
|
|
val reactions = item.reactions?.filter { it.count > 0L }?.notEmpty()
|
|
if(reactions != null) {
|
|
|
|
var lastButton : View? = null
|
|
|
|
val options = DecodeOptions(
|
|
activity,
|
|
column.access_info,
|
|
decodeEmoji = true,
|
|
enlargeEmoji = 1.5f
|
|
)
|
|
|
|
val actMain = activity
|
|
val disableEmojiAnimation = Pref.bpDisableEmojiAnimation(actMain.pref)
|
|
|
|
for(reaction in reactions) {
|
|
|
|
val url = if(disableEmojiAnimation) {
|
|
reaction.static_url.notEmpty() ?: reaction.url.notEmpty()
|
|
} else {
|
|
reaction.url.notEmpty() ?: reaction.static_url.notEmpty()
|
|
}
|
|
|
|
val b = Button(activity).also { btn ->
|
|
btn.layoutParams = FlexboxLayout.LayoutParams(
|
|
FlexboxLayout.LayoutParams.WRAP_CONTENT,
|
|
buttonHeight
|
|
).apply {
|
|
endMargin = marginBetween
|
|
bottomMargin = marginBottom
|
|
}
|
|
btn.minWidthCompat = buttonHeight
|
|
|
|
btn.allCaps = false
|
|
btn.tag = reaction
|
|
|
|
btn.background = if(reaction.me == true) {
|
|
getAdaptiveRippleDrawableRound(
|
|
actMain,
|
|
getAttributeColor(actMain, R.attr.colorButtonBgCw),
|
|
getAttributeColor(actMain, R.attr.colorRippleEffect)
|
|
)
|
|
} else {
|
|
ContextCompat.getDrawable(actMain, R.drawable.btn_bg_transparent_round6dp)
|
|
}
|
|
|
|
btn.setTextColor(content_color)
|
|
|
|
btn.setPadding(paddingH, paddingV, paddingH, paddingV)
|
|
|
|
|
|
btn.text = if(url == null) {
|
|
EmojiDecoder.decodeEmoji(options, "${reaction.name} ${reaction.count}")
|
|
} else {
|
|
SpannableStringBuilder("${reaction.name} ${reaction.count}").also { sb ->
|
|
sb.setSpan(
|
|
NetworkEmojiSpan(url, scale = 1.5f),
|
|
0,
|
|
reaction.name.length,
|
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
)
|
|
val invalidator =
|
|
NetworkEmojiInvalidator(actMain.handler, btn)
|
|
invalidator.register(sb)
|
|
extra_invalidator_list.add(invalidator)
|
|
}
|
|
}
|
|
|
|
btn.setOnClickListener {
|
|
if(reaction.me == true) {
|
|
removeReaction(item, reaction.name)
|
|
} else {
|
|
addReaction(item, TootAnnouncement.Reaction(jsonObject {
|
|
put("name", reaction.name)
|
|
put("count", 1)
|
|
put("me", true)
|
|
putNotNull("url", reaction.url)
|
|
putNotNull("static_url", reaction.static_url)
|
|
}))
|
|
}
|
|
}
|
|
}
|
|
box.addView(b)
|
|
lastButton = b
|
|
|
|
}
|
|
|
|
lastButton
|
|
?.layoutParams
|
|
?.cast<ViewGroup.MarginLayoutParams>()
|
|
?.endMargin = 0
|
|
}
|
|
|
|
llAnnouncementExtra.addView(box)
|
|
}
|
|
|
|
private fun addReaction(item : TootAnnouncement, sample : TootAnnouncement.Reaction?) {
|
|
val column = column ?: return
|
|
if(sample == null) {
|
|
EmojiPicker(activity, column.access_info) { name, _, _, unicode, customEmoji ->
|
|
log.d("addReaction: $name")
|
|
addReaction(item, TootAnnouncement.Reaction(jsonObject {
|
|
put("name", unicode ?: name)
|
|
put("count", 1)
|
|
put("me", true)
|
|
// 以下はカスタム絵文字のみ
|
|
if(customEmoji != null) {
|
|
putNotNull("url", customEmoji.url)
|
|
putNotNull("static_url", customEmoji.static_url)
|
|
}
|
|
}))
|
|
}.show()
|
|
return
|
|
}
|
|
TootTaskRunner(activity).run(column.access_info, object : TootTask {
|
|
override fun background(client : TootApiClient) : TootApiResult? {
|
|
return client.request(
|
|
"/api/v1/announcements/${item.id}/reactions/${sample.name.encodePercent()}",
|
|
JsonObject().toPutRequestBuilder()
|
|
)
|
|
// 200 {}
|
|
}
|
|
|
|
override fun handleResult(result : TootApiResult?) {
|
|
result ?: return
|
|
if(result.jsonObject == null) {
|
|
showToast(activity, true, result.error)
|
|
} else {
|
|
sample.count = 0
|
|
val list = item.reactions
|
|
if(list == null) {
|
|
item.reactions = mutableListOf(sample)
|
|
} else {
|
|
val reaction = list.find { it.name == sample.name }
|
|
if(reaction == null) {
|
|
list.add(sample)
|
|
} else {
|
|
reaction.me = true
|
|
++ reaction.count
|
|
}
|
|
}
|
|
column.announcementUpdated = SystemClock.elapsedRealtime()
|
|
showAnnouncements()
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
private fun removeReaction(item : TootAnnouncement, name : String) {
|
|
val column = column ?: return
|
|
TootTaskRunner(activity).run(column.access_info, object : TootTask {
|
|
override fun background(client : TootApiClient) : TootApiResult? {
|
|
return client.request(
|
|
"/api/v1/announcements/${item.id}/reactions/${name.encodePercent()}",
|
|
JsonObject().toDeleteRequestBuilder()
|
|
)
|
|
// 200 {}
|
|
}
|
|
|
|
override fun handleResult(result : TootApiResult?) {
|
|
result ?: return
|
|
if(result.jsonObject == null) {
|
|
showToast(activity, true, result.error)
|
|
} else {
|
|
val it = item.reactions?.iterator() ?: return
|
|
while(it.hasNext()) {
|
|
val reaction = it.next()
|
|
if(reaction.name == name) {
|
|
reaction.me = false
|
|
if(-- reaction.count <= 0) it.remove()
|
|
break
|
|
}
|
|
}
|
|
column.announcementUpdated = SystemClock.elapsedRealtime()
|
|
showAnnouncements()
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|