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

1315 lines
52 KiB
Kotlin

package jp.juggler.subwaytooter.columnviewholder
import android.annotation.SuppressLint
import android.content.res.ColorStateList
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Path
import android.text.InputType
import android.text.SpannableStringBuilder
import android.view.*
import android.view.inputmethod.EditorInfo
import android.widget.*
import androidx.appcompat.widget.AppCompatButton
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.*
import jp.juggler.subwaytooter.column.*
import jp.juggler.subwaytooter.pref.PrefB
import jp.juggler.subwaytooter.streaming.*
import jp.juggler.subwaytooter.table.daoAcctColor
import jp.juggler.subwaytooter.util.*
import jp.juggler.subwaytooter.view.MyLinkMovementMethod
import jp.juggler.subwaytooter.view.MyTextView
import jp.juggler.subwaytooter.view.OutsideDrawerLayout
import jp.juggler.util.*
import jp.juggler.util.data.*
import jp.juggler.util.log.Benchmark
import jp.juggler.util.log.LogCategory
import jp.juggler.util.ui.CustomTextWatcher
import jp.juggler.util.ui.attrColor
import jp.juggler.util.ui.scan
import kotlinx.coroutines.*
import org.jetbrains.anko.*
import org.jetbrains.anko.custom.customView
import java.lang.Runnable
import java.lang.reflect.Field
@SuppressLint("ClickableViewAccessibility")
class ColumnViewHolder(
val activity: ActMain,
parent: ViewGroup,
) : View.OnClickListener,
SwipyRefreshLayout.OnRefreshListener,
CompoundButton.OnCheckedChangeListener, View.OnLongClickListener {
companion object {
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
var statusAdapter: ItemListAdapter? = null
var pageIdx: Int = 0
lateinit var llLoading: View
lateinit var btnConfirmMail: AppCompatButton
lateinit var tvLoading: MyTextView
lateinit var listView: RecyclerView
lateinit var refreshLayout: SwipyRefreshLayout
lateinit var listLayoutManager: LinearLayoutManager
lateinit var llColumnHeader: View
lateinit var tvColumnIndex: MyTextView
lateinit var tvColumnStatus: MyTextView
private lateinit var tvColumnContext: MyTextView
lateinit var ivColumnIcon: ImageView
lateinit var tvColumnName: MyTextView
lateinit var llColumnSetting: View
private lateinit var llColumnSettingInside: LinearLayout
lateinit var btnSearch: ImageButton
lateinit var btnSearchClear: ImageButton
lateinit var btnEmojiAdd: ImageButton
lateinit var etSearch: EditText
lateinit var flEmoji: FlexboxLayout
lateinit var tvEmojiDesc: MyTextView
lateinit var cbResolve: CheckBox
lateinit var etRegexFilter: EditText
lateinit var tvRegexFilterError: MyTextView
lateinit var btnAnnouncementsBadge: ImageView
lateinit var btnAnnouncements: ImageButton
lateinit var btnAnnouncementsCutout: Paint
lateinit var btnColumnSetting: ImageButton
lateinit var btnColumnReload: ImageButton
lateinit var btnColumnClose: ImageButton
lateinit var flColumnBackground: View
lateinit var ivColumnBackgroundImage: ImageView
lateinit var llSearch: View
lateinit var cbDontCloseColumn: CheckBox
lateinit var cbShowMediaDescription: CheckBox
lateinit var cbRemoteOnly: CheckBox
lateinit var cbWithAttachment: CheckBox
lateinit var cbWithHighlight: CheckBox
lateinit var cbDontShowBoost: CheckBox
lateinit var cbDontShowFollow: CheckBox
lateinit var cbDontShowFavourite: CheckBox
lateinit var cbDontShowReply: CheckBox
lateinit var cbDontShowReaction: CheckBox
lateinit var cbDontShowVote: CheckBox
lateinit var cbDontShowNormalToot: CheckBox
lateinit var cbDontShowNonPublicToot: CheckBox
lateinit var cbInstanceLocal: CheckBox
lateinit var cbDontStreaming: CheckBox
lateinit var cbDontAutoRefresh: CheckBox
lateinit var cbHideMediaDefault: CheckBox
lateinit var cbSystemNotificationNotRelated: CheckBox
lateinit var cbEnableSpeech: CheckBox
lateinit var cbOldApi: CheckBox
lateinit var llRegexFilter: View
lateinit var btnDeleteNotification: AppCompatButton
lateinit var btnColor: AppCompatButton
lateinit var btnLanguageFilter: AppCompatButton
lateinit var svQuickFilter: HorizontalScrollView
lateinit var btnQuickFilterAll: AppCompatButton
lateinit var btnQuickFilterMention: ImageButton
lateinit var btnQuickFilterFavourite: ImageButton
lateinit var btnQuickFilterBoost: ImageButton
lateinit var btnQuickFilterFollow: ImageButton
lateinit var btnQuickFilterPost: ImageButton
lateinit var btnQuickFilterReaction: ImageButton
lateinit var btnQuickFilterVote: ImageButton
lateinit var llRefreshError: FrameLayout
private lateinit var ivRefreshError: ImageView
lateinit var tvRefreshError: MyTextView
lateinit var llListList: View
lateinit var etListName: EditText
lateinit var btnListAdd: View
lateinit var llHashtagExtra: LinearLayout
lateinit var etHashtagExtraAny: EditText
lateinit var etHashtagExtraAll: EditText
lateinit var etHashtagExtraNone: EditText
lateinit var llAnnouncementsBox: View
lateinit var tvAnnouncementsCaption: MyTextView
lateinit var tvAnnouncementsIndex: MyTextView
lateinit var btnAnnouncementsPrev: ImageButton
lateinit var btnAnnouncementsNext: ImageButton
lateinit var llAnnouncements: View
lateinit var tvAnnouncementPeriod: MyTextView
lateinit var tvAnnouncementContent: MyTextView
lateinit var llAnnouncementExtra: LinearLayout
var lastAnnouncementShown = 0L
var bindingBusy: Boolean = false
var lastImageUri: String? = null
var lastImageBitmap: Bitmap? = null
var lastImageTask: Job? = null
var bRefreshErrorWillShown = false
val extraInvalidatorList = ArrayList<NetworkEmojiInvalidator>()
val emojiQueryInvalidatorList = ArrayList<NetworkEmojiInvalidator>()
val announcementContentInvalidator: NetworkEmojiInvalidator
val viewRoot: View = inflate(activity, parent)
/////////////////////////////////
val scrollPosition: ScrollPosition
get() = ScrollPosition(this)
val isColumnSettingShown: Boolean
get() = llColumnSetting.visibility == View.VISIBLE
// val headerView : HeaderViewHolderBase?
// get() = status_adapter?.header
/////////////////////////////////
val isPageDestroyed: Boolean
get() = column == null || activity.isFinishing
/////////////////////////////////
private val procStartLoading: Runnable = Runnable {
if (bindingBusy || isPageDestroyed) return@Runnable
column?.startLoading()
}
val procShowColumnHeader: Runnable = Runnable {
val column = this.column
if (column == null || column.isDispose.get()) return@Runnable
val ac = daoAcctColor.load(column.accessInfo)
tvColumnContext.text = ac.nickname
tvColumnContext.setTextColor(
ac.colorFg.notZero()
?: activity.attrColor(R.attr.colorTimeSmall)
)
tvColumnContext.setBackgroundColor(ac.colorBg)
tvColumnContext.setPaddingRelative(activity.acctPadLr, 0, activity.acctPadLr, 0)
tvColumnName.text = column.getColumnName(false)
showColumnCloseButton()
showAnnouncements(force = false)
}
val procRestoreScrollPosition = 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 [$pageIdx], column==null")
return
}
if (column.isDispose.get()) {
log.d("restoreScrollPosition [$pageIdx], column is disposed")
return
}
if (column.hasMultipleViewHolder()) {
log.d("restoreScrollPosition [$pageIdx] ${column.getColumnName(true)}, column has multiple view holder. retry later.")
// タブレットモードでカラムを追加/削除した際に発生する。
// このタイミングでスクロール位置を復元してもうまくいかないので延期する
activity.handler.postDelayed(this, 100L)
return
}
//復元後にもここを通るがこれは正常である
val sp = column.scrollSave
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 [$pageIdx] ${column.getColumnName(true)} , column has no saved scroll position.")
return
}
column.scrollSave = null
if (listView.visibility != View.VISIBLE) {
log.d("restoreScrollPosition [$pageIdx] ${column.getColumnName(true)} , listView is not visible. saved position ${sp.adapterIndex},${sp.offset} is dropped.")
} else {
log.d("restoreScrollPosition [$pageIdx] ${column.getColumnName(true)} , listView is visible. resume ${sp.adapterIndex},${sp.offset}")
sp.restore(this@ColumnViewHolder)
}
}
}
val procShowColumnStatus: Runnable = Runnable {
val column = this.column
if (column == null || column.isDispose.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 -> "?"
}
)
}
val streamStatus = column.getStreamingStatus()
log.d(
"procShowColumnStatus: streamStatus=$streamStatus, column=${column.accessInfo.acct}/${
column.getColumnName(
true
)
}"
)
when (streamStatus) {
StreamStatus.Missing, StreamStatus.Closed -> {
}
StreamStatus.Connecting, StreamStatus.Open -> {
sb.appendColorShadeIcon(activity, R.drawable.ic_pulse, "Streaming")
sb.append("?")
}
StreamStatus.Subscribed -> {
sb.appendColorShadeIcon(activity, R.drawable.ic_pulse, "Streaming")
}
}
} finally {
log.d("showColumnStatus $sb")
tvColumnStatus.text = sb
}
}
init {
viewRoot.scan { v ->
try {
// ボタンではないTextViewのフォントを変更する
if (v is MyTextView && v !is Button) {
v.typeface = ActMain.timelineFont
}
} catch (ex: Throwable) {
log.e(ex, "can't change typeface.")
}
}
if (PrefB.bpShareViewPool.value) {
listView.setRecycledViewPool(activity.viewPool)
}
listView.itemAnimator = null
//
// val animator = listView.itemAnimator
// if( animator is DefaultItemAnimator){
// animator.supportsChangeAnimations = false
// }
etListName.setOnEditorActionListener { _, actionId, _ ->
var handled = false
if (actionId == EditorInfo.IME_ACTION_SEND) {
btnListAdd.performClick()
handled = true
}
handled
}
refreshLayout.setOnRefreshListener(this)
refreshLayout.setDistanceToTriggerSync((0.5f + 20f * activity.density).toInt())
arrayOf(
btnAnnouncements,
btnAnnouncementsNext,
btnAnnouncementsPrev,
btnColor,
btnColumnClose,
btnColumnReload,
btnColumnSetting,
btnConfirmMail,
btnDeleteNotification,
btnEmojiAdd,
btnLanguageFilter,
btnListAdd,
btnQuickFilterAll,
btnQuickFilterBoost,
btnQuickFilterFavourite,
btnQuickFilterFollow,
btnQuickFilterMention,
btnQuickFilterPost,
btnQuickFilterReaction,
btnQuickFilterVote,
btnSearch,
btnSearchClear,
llColumnHeader,
llRefreshError,
).forEach { it.setOnClickListener(this) }
btnColumnClose.setOnLongClickListener(this)
arrayOf(
cbDontAutoRefresh,
cbDontCloseColumn,
cbShowMediaDescription,
cbDontShowBoost,
cbDontShowFavourite,
cbDontShowFollow,
cbDontShowNonPublicToot,
cbDontShowNormalToot,
cbDontShowReaction,
cbDontShowReply,
cbDontShowVote,
cbDontStreaming,
cbEnableSpeech,
cbHideMediaDefault,
cbInstanceLocal,
cbOldApi,
cbRemoteOnly,
cbSystemNotificationNotRelated,
cbWithAttachment,
cbWithHighlight,
).forEach { it.setOnCheckedChangeListener(this) }
if (PrefB.bpMoveNotificationsQuickFilter.value) {
(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.headerTextSizeSp.isNaN()) {
tvColumnName.textSize = activity.headerTextSizeSp
val acctSize = activity.headerTextSizeSp * 0.857f
tvColumnContext.textSize = acctSize
tvColumnStatus.textSize = acctSize
tvColumnIndex.textSize = acctSize
tvEmojiDesc.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)
etSearch.setOnEditorActionListener(TextView.OnEditorActionListener { _, actionId, _ ->
if (!bindingBusy) {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
btnSearch.performClick()
return@OnEditorActionListener true
}
}
false
})
// 入力の追跡
etRegexFilter.addTextChangedListener(CustomTextWatcher {
if (bindingBusy || isPageDestroyed) return@CustomTextWatcher
if (!isRegexValid()) return@CustomTextWatcher
column?.regexText = etRegexFilter.text.toString()
activity.appState.saveColumnList()
activity.handler.removeCallbacks(procStartLoading)
activity.handler.postDelayed(procStartLoading, 666L)
})
etHashtagExtraAny.addTextChangedListener(CustomTextWatcher {
if (bindingBusy || isPageDestroyed) return@CustomTextWatcher
column?.hashtagAny = etHashtagExtraAny.text.toString()
activity.appState.saveColumnList()
activity.handler.removeCallbacks(procStartLoading)
activity.handler.postDelayed(procStartLoading, 666L)
})
etHashtagExtraAll.addTextChangedListener(CustomTextWatcher {
if (bindingBusy || isPageDestroyed) return@CustomTextWatcher
column?.hashtagAll = etHashtagExtraAll.text.toString()
activity.appState.saveColumnList()
activity.handler.removeCallbacks(procStartLoading)
activity.handler.postDelayed(procStartLoading, 666L)
})
etHashtagExtraNone.addTextChangedListener(CustomTextWatcher {
if (bindingBusy || isPageDestroyed) return@CustomTextWatcher
column?.hashtagNone = etHashtagExtraNone.text.toString()
activity.appState.saveColumnList()
activity.handler.removeCallbacks(procStartLoading)
activity.handler.postDelayed(procStartLoading, 666L)
})
announcementContentInvalidator =
NetworkEmojiInvalidator(activity.handler, tvAnnouncementContent)
tvAnnouncementContent.movementMethod = MyLinkMovementMethod
}
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 onClick(v: View?) = onClickImpl(v)
override fun onLongClick(v: View?): Boolean = onLongClickImpl(v)
override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) =
onCheckedChangedImpl(buttonView, isChecked)
private fun _LinearLayout.inflateColumnHeader() {
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 = myTextView {
gravity = Gravity.END
startPadding = dip(4)
endPadding = dip(4)
textColor = context.attrColor(R.attr.colorColumnHeaderAcct)
textSize = 12f
}.lparams(0, wrapContent) {
weight = 1f
}
tvColumnStatus = myTextView {
gravity = Gravity.END
textColor = context.attrColor(R.attr.colorColumnHeaderPageNumber)
textSize = 12f
}.lparams(wrapContent, wrapContent) {
marginStart = dip(12)
}
tvColumnIndex = myTextView {
gravity = Gravity.END
textColor = context.attrColor(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 = myTextView {
// 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 =
context.attrColor(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
}
private fun _LinearLayout.inflateColumnSetting() {
var label: MyTextView? = null
llColumnSetting = maxHeightScrollView {
lparams(matchParent, wrapContent)
isScrollbarFadingEnabled = false
maxHeight = dip(240)
backgroundColor =
context.attrColor(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 = myTextView {
textColor =
context.attrColor(R.attr.colorColumnHeaderPageNumber)
text = context.getString(R.string.hashtag_extra_any)
}.lparams(matchParent, wrapContent)
etHashtagExtraAny = myEditText {
id = View.generateViewId()
inputType = InputType.TYPE_CLASS_TEXT
maxLines = 1
setHorizontallyScrolling(true)
isHorizontalScrollBarEnabled = true
}.lparams(matchParent, wrapContent)
label?.labelFor = etHashtagExtraAny.id
label = myTextView {
textColor =
context.attrColor(R.attr.colorColumnHeaderPageNumber)
text = context.getString(R.string.hashtag_extra_all)
}.lparams(matchParent, wrapContent)
etHashtagExtraAll = myEditText {
id = View.generateViewId()
inputType = InputType.TYPE_CLASS_TEXT
maxLines = 1
setHorizontallyScrolling(true)
isHorizontalScrollBarEnabled = true
}.lparams(matchParent, wrapContent)
label?.labelFor = etHashtagExtraAll.id
label = myTextView {
textColor =
context.attrColor(R.attr.colorColumnHeaderPageNumber)
text = context.getString(R.string.hashtag_extra_none)
}.lparams(matchParent, wrapContent)
etHashtagExtraNone = myEditText {
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)
cbShowMediaDescription = checkBox {
text = context.getString(R.string.show_media_description)
}.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)
cbWithAttachment = checkBox {
text = context.getString(R.string.with_attachment)
}.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 = myTextView {
textColor =
context.attrColor(R.attr.colorColumnHeaderPageNumber)
text = context.getString(R.string.regex_filter)
}.lparams(wrapContent, wrapContent)
tvRegexFilterError = myTextView {
textColor = context.attrColor(R.attr.colorRegexFilterError)
}.lparams(0, wrapContent) {
weight = 1f
startMargin = dip(4)
}
}
etRegexFilter = myEditText {
id = View.generateViewId()
inputType = InputType.TYPE_CLASS_TEXT
maxLines = 1
setHorizontallyScrolling(true)
isHorizontalScrollBarEnabled = true
}.lparams(matchParent, wrapContent)
label?.labelFor = etRegexFilter.id
btnDeleteNotification = compatButton {
isAllCaps = false
text = context.getString(R.string.notification_delete)
}.lparams(matchParent, wrapContent)
btnColor = compatButton {
isAllCaps = false
text = context.getString(R.string.color_and_background)
}.lparams(matchParent, wrapContent)
btnLanguageFilter = compatButton {
isAllCaps = false
text = context.getString(R.string.language_filter)
}.lparams(matchParent, wrapContent)
}
} // end of column setting scroll view
}
private fun _LinearLayout.inflateAnnouncementsBox() {
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 padLr = dip(6)
setPadding(padLr, 0, padLr, 0)
background = ContextCompat.getDrawable(
context,
R.drawable.btn_bg_transparent_round6dp
)
gravity = Gravity.CENTER_VERTICAL or Gravity.END
tvAnnouncementsCaption = myTextView {
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 = myTextView {
}.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 padLr = dip(6)
val padTb = dip(2)
setPadding(padLr, padTb, padLr, padTb)
scrollBarStyle = View.SCROLLBARS_OUTSIDE_OVERLAY
isScrollbarFadingEnabled = false
maxHeight = dip(240)
verticalLayout {
lparams(matchParent, wrapContent)
// 期間があれば表示する
tvAnnouncementPeriod = myTextView {
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)
}
}
}
}
}
}
private fun _LinearLayout.inflateSearchBar() {
llSearch = verticalLayout {
lparams(matchParent, wrapContent)
linearLayout {
lparams(matchParent, wrapContent)
isBaselineAligned = false
gravity = Gravity.CENTER
etSearch = myEditText {
id = View.generateViewId()
imeOptions = EditorInfo.IME_ACTION_SEARCH
inputType = InputType.TYPE_CLASS_TEXT
maxLines = 1
}.lparams(0, wrapContent) {
weight = 1f
}
flEmoji = flexboxLayout {
flexWrap = FlexWrap.WRAP
justifyContent = JustifyContent.FLEX_START
}.lparams(0, wrapContent) {
weight = 1f
}
btnEmojiAdd = imageButton {
backgroundResource = R.drawable.btn_bg_transparent_round6dp
contentDescription = context.getString(R.string.add)
imageResource = R.drawable.ic_add
imageTintList = ColorStateList.valueOf(
context.attrColor(R.attr.colorTextContent)
)
}.lparams(dip(40), dip(40)) {
startMargin = dip(4)
}
btnSearchClear = imageButton {
backgroundResource = R.drawable.btn_bg_transparent_round6dp
contentDescription = context.getString(R.string.clear)
imageResource = R.drawable.ic_close
imageTintList = ColorStateList.valueOf(
context.attrColor(R.attr.colorTextContent)
)
}.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(
context.attrColor(R.attr.colorTextContent)
)
}.lparams(dip(40), dip(40)) {
startMargin = dip(4)
}
}
cbResolve = checkBox {
text = context.getString(R.string.resolve_non_local_account)
}.lparams(wrapContent, wrapContent) // チェックボックスの余白はタッチ判定外
tvEmojiDesc = myTextView {
text = context.getString(R.string.long_tap_to_delete)
textColor = context.attrColor(R.attr.colorColumnHeaderPageNumber)
textSize = 12f
}.lparams(wrapContent, wrapContent)
} // end of search bar
}
private fun _LinearLayout.inflateListBar() {
llListList = linearLayout {
lparams(matchParent, wrapContent)
isBaselineAligned = false
gravity = Gravity.CENTER
etListName = myEditText {
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(
context.attrColor(R.attr.colorTextContent)
)
}.lparams(dip(40), dip(40)) {
startMargin = dip(4)
}
} // end of list bar header
}
private fun _LinearLayout.inflateQuickFilter() {
svQuickFilter = horizontalScrollView {
lparams(matchParent, wrapContent)
isFillViewport = true
linearLayout {
lparams(matchParent, dip(40))
btnQuickFilterAll = compatButton {
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
}
private fun _LinearLayout.inflateColumnBody(actMain: ActMain) {
flColumnBackground = frameLayout {
ivColumnBackgroundImage = imageView {
importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
scaleType = ImageView.ScaleType.CENTER_CROP
visibility = View.GONE
}.lparams(matchParent, matchParent)
llLoading = verticalLayout {
lparams(matchParent, matchParent)
isBaselineAligned = false
gravity = Gravity.CENTER
tvLoading = myTextView {
gravity = Gravity.CENTER
}.lparams(matchParent, wrapContent)
btnConfirmMail = compatButton {
text = context.getString(R.string.resend_confirm_mail)
background = ContextCompat.getDrawable(
context,
R.drawable.btn_bg_transparent_round6dp
)
}.lparams(matchParent, wrapContent) {
topMargin = dip(8)
}
}
refreshLayout = swipyRefreshLayout {
lparams(matchParent, matchParent)
direction = SwipyRefreshLayoutDirection.BOTH
// スタイルで指定しないとAndroid 6 で落ちる…
listView = recyclerView {
listLayoutManager = LinearLayoutManager(actMain)
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 = myTextView {
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
}
}
fun inflate(actMain: ActMain, parent: ViewGroup) = with(actMain.UI {}) {
val b = Benchmark(log, "Item-Inflate", 40L)
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
}
inflateColumnHeader()
inflateColumnSetting()
inflateAnnouncementsBox()
inflateSearchBar()
inflateListBar()
inflateQuickFilter()
inflateColumnBody(actMain)
}
b.report()
rv
}
}