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

145 lines
5.1 KiB
Kotlin

package jp.juggler.subwaytooter.column
import android.content.Context
import android.content.SharedPreferences
import android.os.SystemClock
import jp.juggler.subwaytooter.api.ApiPath
import jp.juggler.subwaytooter.api.TootApiClient
import jp.juggler.subwaytooter.api.TootApiResult
import jp.juggler.subwaytooter.api.TootParser
import jp.juggler.subwaytooter.api.entity.TimelineItem
import jp.juggler.subwaytooter.api.entity.TootAnnouncement
import jp.juggler.subwaytooter.api.entity.TootInstance
import jp.juggler.subwaytooter.api.entity.parseList
import jp.juggler.subwaytooter.global.appDispatchers
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.util.*
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Job
import kotlinx.coroutines.withContext
import java.util.concurrent.atomic.AtomicBoolean
enum class ColumnTaskType(val marker: Char) {
LOADING('L'),
REFRESH_TOP('T'),
REFRESH_BOTTOM('B'),
GAP('G')
}
abstract class ColumnTask(
val column: Column,
val ctType: ColumnTaskType,
) {
companion object {
private val log = LogCategory("ColumnTask")
}
val ctStarted = AtomicBoolean(false)
val ctClosed = AtomicBoolean(false)
private var job: Job? = null
val isCancelled: Boolean
get() = job?.isCancelled ?: false
var parser = TootParser(context, accessInfo, highlightTrie = highlightTrie)
var listTmp: ArrayList<TimelineItem>? = null
val context: Context
get() = column.context
val accessInfo: SavedAccount
get() = column.accessInfo
val highlightTrie: WordTrieTree?
get() = column.highlightTrie
val isPseudo: Boolean
get() = accessInfo.isPseudo
private val isMastodon: Boolean
get() = accessInfo.isMastodon
val isMisskey: Boolean
get() = accessInfo.isMisskey
val misskeyVersion: Int
get() = accessInfo.misskeyVersion
val pref: SharedPreferences
get() = column.appState.pref
internal fun JsonObject.addMisskeyNotificationFilter() = addMisskeyNotificationFilter(column)
internal fun JsonObject.addRangeMisskey(bBottom: Boolean) = addRangeMisskey(column, bBottom)
internal val profileDirectoryPath: String
get() {
val order = when (val q = column.searchQuery) {
"new" -> q
else -> "active"
}
val local = !column.searchResolve
return "${ApiPath.PATH_PROFILE_DIRECTORY}&order=$order&local=$local"
}
internal suspend fun getAnnouncements(
client: TootApiClient,
force: Boolean = false,
): TootApiResult? {
// announcements is shown only mastodon home timeline, not pseudo.
if (isMastodon && !isPseudo) {
// force (initial loading) always reload announcements
// other (refresh,gap) may reload announcements if last load is too old
if (force || SystemClock.elapsedRealtime() - column.announcementUpdated >= 15 * 60000L) {
if (force) {
log.d("announcements reset")
column.announcements = null
column.announcementUpdated = SystemClock.elapsedRealtime()
}
val (instance, _) = TootInstance.get(client)
if (instance?.versionGE(TootInstance.VERSION_3_1_0_rc1) == true) {
val result = client.request("/api/v1/announcements")
?: return null // cancelled.
when (result.response?.code ?: 0) {
// just skip load announcements for 4xx error if server does not support announcements.
in 400 until 500 -> Unit
else -> {
column.announcements =
parseList(::TootAnnouncement, parser, result.jsonArray)
.notEmpty()
column.announcementUpdated = SystemClock.elapsedRealtime()
client.publishApiProgress("announcements loaded")
// other errors such as network or server fails will stop column loading.
return result
}
}
}
}
}
return TootApiResult()
}
abstract suspend fun background(): TootApiResult?
abstract suspend fun handleResult(result: TootApiResult?)
fun cancel() {
job?.cancel()
}
fun start() {
job = launchMain {
val result = try {
withContext(appDispatchers.io) { background() }
} catch (ignored: CancellationException) {
null // キャンセルされたらresult==nullとする
} catch (ex: Throwable) {
// その他のエラー
TootApiResult(ex.withCaption("error"))
}
handleResult(result)
}
}
}