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

455 lines
13 KiB
Kotlin

package jp.juggler.subwaytooter
import android.annotation.SuppressLint
import android.view.View
import jp.juggler.subwaytooter.api.entity.EntityId
import jp.juggler.subwaytooter.api.entity.TimelineItem
import jp.juggler.subwaytooter.api.entity.TootNotification
import jp.juggler.subwaytooter.api.entity.TootStatus
import jp.juggler.util.getAdaptiveRippleDrawable
import jp.juggler.util.isMainThread
import jp.juggler.util.notZero
import jp.juggler.util.showToast
import org.jetbrains.anko.backgroundDrawable
import kotlin.math.min
///////////////////////////////////////////////////
// ViewHolderとの連携
fun Column.canRemoteOnly() = when (type) {
ColumnType.FEDERATE, ColumnType.FEDERATED_AROUND -> true
else -> false
}
fun Column.canReloadWhenRefreshTop(): Boolean = when (type) {
ColumnType.KEYWORD_FILTER,
ColumnType.SEARCH,
ColumnType.SEARCH_MSP,
ColumnType.SEARCH_TS,
ColumnType.SEARCH_NOTESTOCK,
ColumnType.CONVERSATION,
ColumnType.LIST_LIST,
ColumnType.TREND_TAG,
ColumnType.FOLLOW_SUGGESTION,
ColumnType.PROFILE_DIRECTORY,
-> true
ColumnType.LIST_MEMBER,
ColumnType.MUTES,
ColumnType.FOLLOW_REQUESTS,
-> isMisskey
else -> false
}
// カラム操作的にリフレッシュを許容するかどうか
fun Column.canRefreshTopBySwipe(): Boolean =
canReloadWhenRefreshTop() ||
when (type) {
ColumnType.CONVERSATION,
ColumnType.INSTANCE_INFORMATION,
-> false
else -> true
}
// カラム操作的にリフレッシュを許容するかどうか
fun Column.canRefreshBottomBySwipe(): Boolean = when (type) {
ColumnType.LIST_LIST,
ColumnType.CONVERSATION,
ColumnType.INSTANCE_INFORMATION,
ColumnType.KEYWORD_FILTER,
ColumnType.SEARCH,
ColumnType.TREND_TAG,
ColumnType.FOLLOW_SUGGESTION,
-> false
ColumnType.FOLLOW_REQUESTS -> isMisskey
ColumnType.LIST_MEMBER -> !isMisskey
else -> true
}
// データ的にリフレッシュを許容するかどうか
fun Column.canRefreshTop(): Boolean = when (pagingType) {
ColumnPagingType.Default -> idRecent != null
else -> false
}
// データ的にリフレッシュを許容するかどうか
fun Column.canRefreshBottom(): Boolean = when (pagingType) {
ColumnPagingType.Default, ColumnPagingType.Cursor -> idOld != null
ColumnPagingType.None -> false
ColumnPagingType.Offset -> true
}
fun Column.getIconId(): Int = type.iconId(accessInfo.acct)
fun Column.getColumnName(long: Boolean) =
type.name2(this, long) ?: type.name1(context)
fun Column.getContentColor() = contentColor.notZero() ?: Column.defaultColorContentText
fun Column.getAcctColor() = acctColor.notZero() ?: Column.defaultColorContentAcct
fun Column.getHeaderPageNumberColor() = headerFgColor.notZero() ?: Column.defaultColorHeaderPageNumber
fun Column.getHeaderNameColor() = headerFgColor.notZero() ?: Column.defaultColorHeaderName
fun Column.getHeaderBackgroundColor() = headerBgColor.notZero() ?: Column.defaultColorHeaderBg
fun Column.setHeaderBackground(view: View) {
view.backgroundDrawable = getAdaptiveRippleDrawable(
getHeaderBackgroundColor(),
getHeaderNameColor()
)
}
val Column.hasHashtagExtra: Boolean
get() = when {
isMisskey -> false
type == ColumnType.HASHTAG -> true
// ColumnType.HASHTAG_FROM_ACCT は追加のタグを指定しても結果に反映されない
else -> false
}
fun Column.getHeaderDesc(): String {
var cache = cacheHeaderDesc
if (cache != null) return cache
cache = when (type) {
ColumnType.SEARCH -> context.getString(R.string.search_desc_mastodon_api)
ColumnType.SEARCH_MSP -> loadSearchDesc(
R.raw.search_desc_msp_en,
R.raw.search_desc_msp_ja
)
ColumnType.SEARCH_TS -> loadSearchDesc(
R.raw.search_desc_ts_en,
R.raw.search_desc_ts_ja
)
ColumnType.SEARCH_NOTESTOCK -> loadSearchDesc(
R.raw.search_desc_notestock_en,
R.raw.search_desc_notestock_ja
)
else -> ""
}
cacheHeaderDesc = cache
return cache
}
fun Column.hasMultipleViewHolder(): Boolean = listViewHolder.size > 1
fun Column.addColumnViewHolder(cvh: ColumnViewHolder) {
// 現在のリストにあるなら削除する
removeColumnViewHolder(cvh)
// 最後に追加されたものが先頭にくるようにする
// 呼び出しの後に必ず追加されているようにする
listViewHolder.addFirst(cvh)
}
fun Column.removeColumnViewHolder(cvh: ColumnViewHolder) {
val it = listViewHolder.iterator()
while (it.hasNext()) {
if (cvh == it.next()) it.remove()
}
}
fun Column.removeColumnViewHolderByActivity(activity: ActMain) {
val it = listViewHolder.iterator()
while (it.hasNext()) {
val cvh = it.next()
if (cvh.activity == activity) {
it.remove()
}
}
}
fun Column.fireShowContent(
reason: String,
changeList: List<AdapterChange>? = null,
reset: Boolean = false,
) {
if (!isMainThread) error("fireShowContent: not on main thread.")
viewHolder?.showContent(reason, changeList, reset)
}
fun Column.fireShowColumnHeader() {
if (!isMainThread) error("fireShowColumnHeader: not on main thread.")
viewHolder?.showColumnHeader()
}
fun Column.fireShowColumnStatus() {
if (!isMainThread) error("fireShowColumnStatus: not on main thread.")
viewHolder?.showColumnStatus()
}
fun Column.fireColumnColor() {
if (!isMainThread) error("fireColumnColor: not on main thread.")
viewHolder?.showColumnColor()
}
fun Column.fireRelativeTime() {
if (!isMainThread) error("fireRelativeTime: not on main thread.")
viewHolder?.updateRelativeTime()
}
fun Column.fireRebindAdapterItems() {
if (!isMainThread) error("fireRelativeTime: not on main thread.")
viewHolder?.rebindAdapterItems()
}
/////////////////////////////////////////////////////////////////
// ActMain の表示開始時に呼ばれる
fun Column.onActivityStart() {
// 破棄されたカラムなら何もしない
if (isDispose.get()) {
Column.log.d("onStart: column was disposed.")
return
}
// 未初期化なら何もしない
if (!bFirstInitialized) {
Column.log.d("onStart: column is not initialized.")
return
}
// 初期ロード中なら何もしない
if (bInitialLoading) {
Column.log.d("onStart: column is in initial loading.")
return
}
// フィルタ一覧のリロードが必要
if (filterReloadRequired) {
filterReloadRequired = false
startLoading()
return
}
// 始端リフレッシュの最中だった
// リフレッシュ終了時に自動でストリーミング開始するはず
if (bRefreshingTop) {
Column.log.d("onStart: bRefreshingTop is true.")
return
}
if (!bRefreshLoading &&
canAutoRefresh() &&
!PrefB.bpDontRefreshOnResume(appState.pref) &&
!dontAutoRefresh
) {
// リフレッシュしてからストリーミング開始
Column.log.d("onStart: start auto refresh.")
startRefresh(bSilent = true, bBottom = false)
} else if (isSearchColumn) {
// 検索カラムはリフレッシュもストリーミングもないが、表示開始のタイミングでリストの再描画を行いたい
fireShowContent(reason = "Column onStart isSearchColumn", reset = true)
} else if (canStreamingState() && canStreamingType()) {
// ギャップつきでストリーミング開始
this.bPutGap = true
fireShowColumnStatus()
}
}
fun Column.cancelLastTask() {
if (lastTask != null) {
lastTask?.cancel()
lastTask = null
//
bInitialLoading = false
bRefreshLoading = false
mInitialLoadingError = context.getString(R.string.cancelled)
}
}
// @Nullable String parseMaxId( TootApiResult result ){
// if( result != null && result.link_older != null ){
// Matcher m = reMaxId.matcher( result.link_older );
// if( m.get() ) return m.group( 1 );
// }
// return null;
// }
fun Column.startLoading() {
cancelLastTask()
initFilter()
Column.showOpenSticker = PrefB.bpOpenSticker(appState.pref)
mRefreshLoadingErrorPopupState = 0
mRefreshLoadingError = ""
mInitialLoadingError = ""
bFirstInitialized = true
bInitialLoading = true
bRefreshLoading = false
idOld = null
idRecent = null
offsetNext = 0
pagingType = ColumnPagingType.Default
duplicateMap.clear()
listData.clear()
fireShowContent(reason = "loading start", reset = true)
@SuppressLint("StaticFieldLeak")
val task = ColumnTask_Loading(this)
this.lastTask = task
task.start()
}
fun Column.startRefresh(
bSilent: Boolean,
bBottom: Boolean,
postedStatusId: EntityId? = null,
refreshAfterToot: Int = -1,
) {
if (lastTask != null) {
if (!bSilent) {
context.showToast(true, R.string.column_is_busy)
val holder = viewHolder
if (holder != null) holder.refreshLayout.isRefreshing = false
}
return
} else if (bBottom && !canRefreshBottom()) {
if (!bSilent) {
context.showToast(true, R.string.end_of_list)
val holder = viewHolder
if (holder != null) holder.refreshLayout.isRefreshing = false
}
return
} else if (!bBottom && !canRefreshTop()) {
val holder = viewHolder
if (holder != null) holder.refreshLayout.isRefreshing = false
startLoading()
return
}
if (bSilent) {
val holder = viewHolder
if (holder != null) {
holder.refreshLayout.isRefreshing = true
}
}
if (!bBottom) {
bRefreshingTop = true
}
bRefreshLoading = true
mRefreshLoadingError = ""
@SuppressLint("StaticFieldLeak")
val task = ColumnTask_Refresh(this, bSilent, bBottom, postedStatusId, refreshAfterToot)
this.lastTask = task
task.start()
fireShowColumnStatus()
}
fun Column.startRefreshForPost(
refreshAfterPost: Int,
postedStatusId: EntityId,
postedReplyId: EntityId?,
) {
@Suppress("NON_EXHAUSTIVE_WHEN")
when (type) {
ColumnType.HOME,
ColumnType.LOCAL,
ColumnType.FEDERATE,
ColumnType.DIRECT_MESSAGES,
ColumnType.MISSKEY_HYBRID,
-> startRefresh(
bSilent = true,
bBottom = false,
postedStatusId = postedStatusId,
refreshAfterToot = refreshAfterPost
)
ColumnType.PROFILE -> {
if (profileTab == ProfileTab.Status && profileId == accessInfo.loginAccount?.id) {
startRefresh(
bSilent = true,
bBottom = false,
postedStatusId = postedStatusId,
refreshAfterToot = refreshAfterPost
)
}
}
ColumnType.CONVERSATION -> {
// 会話への返信が行われたなら会話を更新する
try {
if (postedReplyId != null) {
for (item in listData) {
if (item is TootStatus && item.id == postedReplyId) {
startLoading()
break
}
}
}
} catch (_: Throwable) {
}
}
}
}
fun Column.startGap(gap: TimelineItem?, isHead: Boolean) {
if (gap == null) {
context.showToast(true, "gap is null")
return
}
if (lastTask != null) {
context.showToast(true, R.string.column_is_busy)
return
}
@Suppress("UNNECESSARY_SAFE_CALL")
viewHolder?.refreshLayout?.isRefreshing = true
bRefreshLoading = true
mRefreshLoadingError = ""
@SuppressLint("StaticFieldLeak")
val task = ColumnTask_Gap(this, gap, isHead = isHead)
this.lastTask = task
task.start()
fireShowColumnStatus()
}
// misskeyのキャプチャの対象となる投稿IDのリストを作る
// カラム内データの上(最新)から40件をキャプチャ対象とする
fun Column.updateMisskeyCapture() {
if (!isMisskey) return
val streamConnection = appState.streamManager.getConnection(this)
?: return
val max = 40
val list = ArrayList<EntityId>(max * 2) // リブログなどで膨れる場合がある
fun add(s: TootStatus?) {
s ?: return
list.add(s.id)
add(s.reblog)
add(s.reply)
}
for (i in 0 until min(max, listData.size)) {
val o = listData[i]
if (o is TootStatus) {
add(o)
} else if (o is TootNotification) {
add(o.status)
}
}
if (list.isNotEmpty()) streamConnection.misskeySetCapture(list)
}