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

513 lines
16 KiB
Kotlin

package jp.juggler.subwaytooter.actmain
import android.annotation.SuppressLint
import android.content.Intent
import android.content.res.ColorStateList
import android.view.View
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.appcompat.app.AlertDialog
import androidx.recyclerview.widget.RecyclerView
import jp.juggler.subwaytooter.*
import jp.juggler.subwaytooter.column.*
import jp.juggler.subwaytooter.columnviewholder.TabletColumnViewHolder
import jp.juggler.subwaytooter.columnviewholder.scrollToTop2
import jp.juggler.subwaytooter.columnviewholder.showColumnSetting
import jp.juggler.subwaytooter.pref.PrefB
import jp.juggler.subwaytooter.pref.PrefI
import jp.juggler.subwaytooter.pref.PrefS
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.table.daoAcctColor
import jp.juggler.util.*
import jp.juggler.util.data.clip
import jp.juggler.util.log.LogCategory
import jp.juggler.util.log.showToast
import jp.juggler.util.ui.getAdaptiveRippleDrawableRound
import jp.juggler.util.ui.vg
import org.jetbrains.anko.backgroundDrawable
import kotlin.math.abs
import kotlin.math.min
private val log = LogCategory("ActMainColumns")
// スマホモードなら現在のカラムを、タブレットモードなら-1Lを返す
// (カラム一覧画面のデフォルト選択位置に使われる)
val ActMain.currentColumn: Int
get() = phoneTab(
{ it.pager.currentItem },
{ -1 }
)
// 新しいカラムをどこに挿入するか
// 現在のページの次の位置か、終端
val ActMain.defaultInsertPosition: Int
get() = phoneTab(
{ it.pager.currentItem + 1 },
{ Integer.MAX_VALUE }
)
// カラム追加後など、そのカラムにスクロールして初期ロードを行う
fun ActMain.scrollAndLoad(idx: Int) {
val c = appState.column(idx) ?: return
scrollToColumn(idx)
if (!c.bFirstInitialized) c.startLoading()
}
fun ActMain.addColumn(column: Column, indexArg: Int): Int {
val index = indexArg.clip(0, appState.columnCount)
phoneOnly { env -> env.pager.adapter = null }
appState.editColumnList {
it.add(index, column)
}
phoneTab(
{ env -> env.pager.adapter = env.pagerAdapter },
{ env -> resizeColumnWidth(env) }
)
updateColumnStrip()
return index
}
fun ActMain.addColumn(
allowColumnDuplication: Boolean,
indexArg: Int,
ai: SavedAccount,
type: ColumnType,
vararg params: Any,
): Column {
if (!allowColumnDuplication) {
// 既に同じカラムがあればそこに移動する
appState.columnList.forEachIndexed { i, column ->
if (ColumnSpec.isSameSpec(column, ai, type, params)) {
scrollToColumn(i)
return column
}
}
}
//
val col = Column(appState, ai, type.id, *params)
val index = addColumn(col, indexArg)
scrollAndLoad(index)
return col
}
fun ActMain.addColumn(
indexArg: Int,
ai: SavedAccount,
type: ColumnType,
vararg params: Any,
): Column {
return addColumn(
PrefB.bpAllowColumnDuplication.value,
indexArg,
ai,
type,
*params
)
}
fun ActMain.removeColumn(column: Column) {
val idxColumn = appState.columnIndex(column) ?: return
phoneOnly { env -> env.pager.adapter = null }
appState.editColumnList {
it.removeAt(idxColumn).dispose()
}
phoneTab(
{ env -> env.pager.adapter = env.pagerAdapter },
{ env -> resizeColumnWidth(env) }
)
updateColumnStrip()
}
fun ActMain.isVisibleColumn(idx: Int) = phoneTab(
{ env -> env.pager.currentItem == idx },
{ env -> idx >= 0 && idx in env.visibleColumnsIndices },
)
fun ActMain.updateColumnStrip() {
llEmpty.vg(appState.columnCount == 0)
val iconSize = ActMain.stripIconSize
val rootW = (iconSize * 1.25f + 0.5f).toInt()
val rootH = (iconSize * 1.5f + 0.5f).toInt()
val iconTopMargin = (iconSize * 0.125f + 0.5f).toInt()
val barHeight = (iconSize * 0.094f + 0.5f).toInt()
val barTopMargin = (iconSize * 0.094f + 0.5f).toInt()
// 両端のメニューと投稿ボタンの大きさ
val pad = (rootH - iconSize) shr 1
for (btn in arrayOf(btnToot, btnMenu, btnQuickPostMenu, btnQuickToot)) {
btn.layoutParams.width = rootH // not W
btn.layoutParams.height = rootH
btn.setPaddingRelative(pad, pad, pad, pad)
}
llColumnStrip.removeAllViews()
appState.columnList.forEachIndexed { index, column ->
val viewRoot = layoutInflater.inflate(R.layout.lv_column_strip, llColumnStrip, false)
val ivIcon = viewRoot.findViewById<ImageView>(R.id.ivIcon)
val vAcctColor = viewRoot.findViewById<View>(R.id.vAcctColor)
// root: 48x48dp LinearLayout(vertical), gravity=center
viewRoot.layoutParams.width = rootW
viewRoot.layoutParams.height = rootH
// ivIcon: 32x32dp marginTop="4dp" 図柄が32x32dp、パディングなし
ivIcon.layoutParams.width = iconSize
ivIcon.layoutParams.height = iconSize
(ivIcon.layoutParams as? LinearLayout.LayoutParams)?.topMargin = iconTopMargin
// vAcctColor: 32x3dp marginTop="3dp"
vAcctColor.layoutParams.width = iconSize
vAcctColor.layoutParams.height = barHeight
(vAcctColor.layoutParams as? LinearLayout.LayoutParams)?.topMargin = barTopMargin
viewRoot.tag = index
viewRoot.setOnClickListener { v ->
val idx = v.tag as Int
if (PrefB.bpScrollTopFromColumnStrip.value && isVisibleColumn(idx)) {
column.viewHolder?.scrollToTop2()
return@setOnClickListener
}
scrollToColumn(idx)
}
viewRoot.contentDescription = column.getColumnName(true)
viewRoot.backgroundDrawable = getAdaptiveRippleDrawableRound(
this,
column.getHeaderBackgroundColor(),
column.getHeaderNameColor()
)
ivIcon.setImageResource(column.getIconId())
ivIcon.imageTintList = ColorStateList.valueOf(column.getHeaderNameColor())
//
val ac = daoAcctColor.load(column.accessInfo)
if (daoAcctColor.hasColorForeground(ac)) {
vAcctColor.setBackgroundColor(ac.colorFg)
} else {
vAcctColor.visibility = View.INVISIBLE
}
//
llColumnStrip.addView(viewRoot)
}
svColumnStrip.requestLayout()
updateColumnStripSelection(-1, -1f)
}
fun ActMain.closeColumn(column: Column, bConfirmed: Boolean = false) {
if (column.dontClose) {
showToast(false, R.string.column_has_dont_close_option)
return
}
if (!bConfirmed && !PrefB.bpDontConfirmBeforeCloseColumn.value) {
AlertDialog.Builder(this)
.setMessage(R.string.confirm_close_column)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.ok) { _, _ -> closeColumn(column, bConfirmed = true) }
.show()
return
}
appState.columnIndex(column)?.let { page_delete ->
phoneTab({ env ->
val pageShowing = env.pager.currentItem
removeColumn(column)
if (pageShowing == page_delete) {
scrollAndLoad(pageShowing - 1)
}
}, {
removeColumn(column)
scrollAndLoad(page_delete - 1)
})
}
}
fun ActMain.closeColumnAll(oldColumnIndex: Int = -1, bConfirmed: Boolean = false) {
if (!bConfirmed) {
AlertDialog.Builder(this)
.setMessage(R.string.confirm_close_column_all)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.ok) { _, _ -> closeColumnAll(oldColumnIndex, true) }
.show()
return
}
var lastColumnIndex = when (oldColumnIndex) {
-1 -> phoneTab(
{ it.pager.currentItem },
{ 0 }
)
else -> oldColumnIndex
}
phoneOnly { env -> env.pager.adapter = null }
appState.editColumnList { list ->
for (i in list.indices.reversed()) {
val column = list[i]
if (column.dontClose) continue
list.removeAt(i).dispose()
if (lastColumnIndex >= i) --lastColumnIndex
}
}
phoneTab(
{ env -> env.pager.adapter = env.pagerAdapter },
{ env -> resizeColumnWidth(env) }
)
updateColumnStrip()
scrollAndLoad(lastColumnIndex)
}
fun ActMain.closeColumnSetting(): Boolean {
phoneTab({ env ->
val vh = env.pagerAdapter.getColumnViewHolder(env.pager.currentItem)
if (vh?.isColumnSettingShown == true) {
vh.showColumnSetting(false)
return@closeColumnSetting true
}
}, { env ->
for (i in 0 until env.tabletLayoutManager.childCount) {
val columnViewHolder = when (val v = env.tabletLayoutManager.getChildAt(i)) {
null -> null
else -> (env.tabletPager.getChildViewHolder(v) as? TabletColumnViewHolder)?.columnViewHolder
}
if (columnViewHolder?.isColumnSettingShown == true) {
columnViewHolder.showColumnSetting(false)
return@closeColumnSetting true
}
}
})
return false
}
// 新しいカラムをどこに挿入するか
// カラムの次の位置か、現在のページの次の位置か、終端
fun ActMain.nextPosition(column: Column?): Int =
appState.columnIndex(column)?.let { it + 1 } ?: defaultInsertPosition
fun ActMain.isOrderChanged(newOrder: List<Int>): Boolean {
if (newOrder.size != appState.columnCount) return true
for (i in newOrder.indices) {
if (newOrder[i] != i) return true
}
return false
}
fun ActMain.setColumnsOrder(newOrder: List<Int>) {
phoneOnly { env -> env.pager.adapter = null }
appState.editColumnList { list ->
// columns with new order
val tmpList = newOrder.mapNotNull { i -> list.elementAtOrNull(i) }
val usedSet = newOrder.toSet()
list.forEachIndexed { i, v ->
if (!usedSet.contains(i)) v.dispose()
}
list.clear()
list.addAll(tmpList)
}
phoneTab(
{ env -> env.pager.adapter = env.pagerAdapter },
{ env -> resizeColumnWidth(env) }
)
appState.saveColumnList()
updateColumnStrip()
}
fun ActMain.searchFromActivityResult(data: Intent?, columnType: ColumnType) =
data?.string(Intent.EXTRA_TEXT)?.let {
addColumn(
false,
defaultInsertPosition,
SavedAccount.na,
columnType,
it
)
}
fun ActMain.scrollToColumn(index: Int, smoothScroll: Boolean = true) {
scrollColumnStrip(index)
phoneTab(
// スマホはスムーススクロール基本ありだがたまにしない
{ env ->
log.d("ipLastColumnPos beforeScroll=${env.pager.currentItem}")
env.pager.setCurrentItem(index, smoothScroll)
},
// タブレットでスムーススクロールさせると頻繁にオーバーランするので絶対しない
{ env ->
log.d("ipLastColumnPos beforeScroll=${env.visibleColumnsIndices.first}")
env.tabletPager.scrollToPosition(index)
}
)
}
// onCreate時に前回のカラムまでスクロールする
fun ActMain.scrollToLastColumn() {
if (appState.columnCount <= 0) return
val columnPos = PrefI.ipLastColumnPos.value
log.d("ipLastColumnPos load $columnPos")
// 前回最後に表示していたカラムの位置にスクロールする
if (columnPos in 0 until appState.columnCount) {
scrollToColumn(columnPos, false)
}
// 表示位置に合わせたイベントを発行
phoneTab(
{ env -> onPageSelected(env.pager.currentItem) },
{ env -> resizeColumnWidth(env) }
)
}
@SuppressLint("NotifyDataSetChanged")
fun ActMain.resizeColumnWidth(views: ActMainTabletViews) {
var columnWMinDp = ActMain.COLUMN_WIDTH_MIN_DP
val sv = PrefS.spColumnWidth.value
if (sv.isNotEmpty()) {
try {
val iv = Integer.parseInt(sv)
if (iv >= 100) {
columnWMinDp = iv
}
} catch (ex: Throwable) {
log.e(ex, "can't parse spColumnWidth. $sv")
}
}
val dm = resources.displayMetrics
val screenWidth = dm.widthPixels
val density = dm.density
var columnWMin = (0.5f + columnWMinDp * density).toInt()
if (columnWMin < 1) columnWMin = 1
var columnW: Int
if (screenWidth < columnWMin * 2) {
// 最小幅で2つ表示できないのなら1カラム表示
nScreenColumn = 1
columnW = screenWidth
} else {
// カラム最小幅から計算した表示カラム数
nScreenColumn = screenWidth / columnWMin
if (nScreenColumn < 1) nScreenColumn = 1
// データのカラム数より大きくならないようにする
// (でも最小は1)
val columnCount = appState.columnCount
if (columnCount > 0 && columnCount < nScreenColumn) {
nScreenColumn = columnCount
}
// 表示カラム数から計算したカラム幅
columnW = screenWidth / nScreenColumn
// 最小カラム幅の1.5倍よりは大きくならないようにする
val columnWMax = (0.5f + columnWMin * 1.5f).toInt()
if (columnW > columnWMax) {
columnW = columnWMax
}
}
nColumnWidth = columnW // dividerの幅を含む
val dividerWidth = (0.5f + 1f * density).toInt()
columnW -= dividerWidth
views.tabletPagerAdapter.columnWidth = columnW // dividerの幅を含まない
// env.tablet_snap_helper.columnWidth = column_w //使われていない
resizeAutoCW(columnW) // dividerの幅を含まない
// 並べ直す
views.tabletPagerAdapter.notifyDataSetChanged()
}
fun ActMain.scrollColumnStrip(select: Int) {
val childCount = llColumnStrip.childCount
if (select < 0 || select >= childCount) {
return
}
val icon = llColumnStrip.getChildAt(select)
val svWidth = (llColumnStrip.parent as View).width
val llWidth = llColumnStrip.width
val iconWidth = icon.width
val iconLeft = icon.left
if (svWidth == 0 || llWidth == 0 || iconWidth == 0) {
handler.postDelayed({ scrollColumnStrip(select) }, 20L)
}
val sx = iconLeft + iconWidth / 2 - svWidth / 2
svColumnStrip.smoothScrollTo(sx, 0)
}
fun ActMain.updateColumnStripSelection(position: Int, positionOffset: Float) {
handler.post(Runnable {
if (isFinishing) return@Runnable
if (appState.columnCount == 0) {
llColumnStrip.setVisibleRange(-1, -1, 0f)
} else {
phoneTab({ env ->
if (position >= 0) {
llColumnStrip.setVisibleRange(position, position, positionOffset)
} else {
val c = env.pager.currentItem
llColumnStrip.setVisibleRange(c, c, 0f)
}
}, { env ->
val vs = env.tabletLayoutManager.findFirstVisibleItemPosition()
val ve = env.tabletLayoutManager.findLastVisibleItemPosition()
val vr = if (vs == RecyclerView.NO_POSITION || ve == RecyclerView.NO_POSITION) {
IntRange(-1, -2) // empty and less than zero
} else {
IntRange(vs, min(ve, vs + nScreenColumn - 1))
}
var slideRatio = 0f
if (vr.first <= vr.last) {
val child = env.tabletLayoutManager.findViewByPosition(vr.first)
slideRatio =
(abs((child?.left ?: 0) / nColumnWidth.toFloat())).clip(0f, 1f)
}
llColumnStrip.setVisibleRange(vr.first, vr.last, slideRatio)
})
}
})
}
fun ActMain.showColumnMatchAccount(account: SavedAccount) {
appState.columnList.forEach { column ->
if (account == column.accessInfo) {
column.fireRebindAdapterItems()
}
}
}