「会話と参照」カラムを表示する
This commit is contained in:
parent
eeedf742a6
commit
406ce8e5c5
|
@ -1,7 +1,8 @@
|
||||||
package jp.juggler.subwaytooter.action
|
package jp.juggler.subwaytooter.action
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import jp.juggler.subwaytooter.*
|
import jp.juggler.subwaytooter.ActMain
|
||||||
|
import jp.juggler.subwaytooter.R
|
||||||
import jp.juggler.subwaytooter.actmain.addColumn
|
import jp.juggler.subwaytooter.actmain.addColumn
|
||||||
import jp.juggler.subwaytooter.api.*
|
import jp.juggler.subwaytooter.api.*
|
||||||
import jp.juggler.subwaytooter.api.entity.*
|
import jp.juggler.subwaytooter.api.entity.*
|
||||||
|
@ -14,7 +15,6 @@ import jp.juggler.subwaytooter.table.SavedAccount
|
||||||
import jp.juggler.subwaytooter.util.matchHost
|
import jp.juggler.subwaytooter.util.matchHost
|
||||||
import jp.juggler.subwaytooter.util.openCustomTab
|
import jp.juggler.subwaytooter.util.openCustomTab
|
||||||
import jp.juggler.util.*
|
import jp.juggler.util.*
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
private val log = LogCategory("Action_Conversation")
|
private val log = LogCategory("Action_Conversation")
|
||||||
|
|
||||||
|
@ -45,7 +45,12 @@ fun ActMain.clickConversation(
|
||||||
}
|
}
|
||||||
|
|
||||||
// プレビューカードのイメージは返信かもしれない
|
// プレビューカードのイメージは返信かもしれない
|
||||||
fun ActMain.clickCardImage(pos: Int, accessInfo: SavedAccount, card: TootCard?, longClick: Boolean = false) {
|
fun ActMain.clickCardImage(
|
||||||
|
pos: Int,
|
||||||
|
accessInfo: SavedAccount,
|
||||||
|
card: TootCard?,
|
||||||
|
longClick: Boolean = false,
|
||||||
|
) {
|
||||||
card ?: return
|
card ?: return
|
||||||
card.originalStatus?.let {
|
card.originalStatus?.let {
|
||||||
if (longClick) {
|
if (longClick) {
|
||||||
|
@ -80,7 +85,7 @@ fun ActMain.clickReplyInfo(
|
||||||
|
|
||||||
// tootsearchは返信元のIDを取得するのにひと手間必要
|
// tootsearchは返信元のIDを取得するのにひと手間必要
|
||||||
columnType == ColumnType.SEARCH_TS ||
|
columnType == ColumnType.SEARCH_TS ||
|
||||||
columnType == ColumnType.SEARCH_NOTESTOCK ->
|
columnType == ColumnType.SEARCH_NOTESTOCK ->
|
||||||
conversationFromTootsearch(pos, statusShowing)
|
conversationFromTootsearch(pos, statusShowing)
|
||||||
|
|
||||||
else ->
|
else ->
|
||||||
|
@ -140,14 +145,26 @@ fun ActMain.conversationLocal(
|
||||||
pos: Int,
|
pos: Int,
|
||||||
accessInfo: SavedAccount,
|
accessInfo: SavedAccount,
|
||||||
statusId: EntityId,
|
statusId: EntityId,
|
||||||
) = addColumn(pos, accessInfo, ColumnType.CONVERSATION, statusId)
|
isReference: Boolean = false,
|
||||||
|
) = addColumn(
|
||||||
|
pos,
|
||||||
|
accessInfo,
|
||||||
|
when {
|
||||||
|
isReference && TootInstance.getCached(accessInfo)?.canUseReference == true ->
|
||||||
|
ColumnType.CONVERSATION_WITH_REFERENCE
|
||||||
|
else ->
|
||||||
|
ColumnType.CONVERSATION
|
||||||
|
},
|
||||||
|
statusId,
|
||||||
|
)
|
||||||
|
|
||||||
private val reDetailedStatusTime =
|
private val reDetailedStatusTime =
|
||||||
"""<a\b[^>]*?\bdetailed-status__datetime\b[^>]*href="https://[^/]+/@[^/]+/([^\s?#/"]+)"""
|
"""<a\b[^>]*?\bdetailed-status__datetime\b[^>]*href="https://[^/]+/@[^/]+/([^\s?#/"]+)"""
|
||||||
.toRegex()
|
.toRegex()
|
||||||
|
|
||||||
private val reHeaderOgUrl = """<meta\s+content="https://[^/"]+/notice/([^/"]+)"\s+property="og:url"/?>"""
|
private val reHeaderOgUrl =
|
||||||
.toRegex()
|
"""<meta\s+content="https://[^/"]+/notice/([^/"]+)"\s+property="og:url"/?>"""
|
||||||
|
.toRegex()
|
||||||
|
|
||||||
// 疑似アカウントではURLからIDを取得するのにHTMLと正規表現を使う
|
// 疑似アカウントではURLからIDを取得するのにHTMLと正規表現を使う
|
||||||
suspend fun guessStatusIdFromPseudoAccount(
|
suspend fun guessStatusIdFromPseudoAccount(
|
||||||
|
@ -190,7 +207,8 @@ private fun ActMain.conversationRemote(
|
||||||
) { client ->
|
) { client ->
|
||||||
if (accessInfo.isPseudo) {
|
if (accessInfo.isPseudo) {
|
||||||
// 疑似アカウントではURLからIDを取得するのにHTMLと正規表現を使う
|
// 疑似アカウントではURLからIDを取得するのにHTMLと正規表現を使う
|
||||||
val pair = guessStatusIdFromPseudoAccount(applicationContext, client, remoteStatusUrl)
|
val pair =
|
||||||
|
guessStatusIdFromPseudoAccount(applicationContext, client, remoteStatusUrl)
|
||||||
localStatusId = pair.second
|
localStatusId = pair.second
|
||||||
pair.first
|
pair.first
|
||||||
} else {
|
} else {
|
||||||
|
@ -218,6 +236,7 @@ fun ActMain.conversationOtherInstance(
|
||||||
statusIdOriginal: EntityId? = null,
|
statusIdOriginal: EntityId? = null,
|
||||||
hostAccess: Host? = null,
|
hostAccess: Host? = null,
|
||||||
statusIdAccess: EntityId? = null,
|
statusIdAccess: EntityId? = null,
|
||||||
|
isReference: Boolean = false,
|
||||||
) {
|
) {
|
||||||
val activity = this
|
val activity = this
|
||||||
|
|
||||||
|
@ -226,7 +245,8 @@ fun ActMain.conversationOtherInstance(
|
||||||
val hostOriginal = Host.parse(url.toUri().authority ?: "")
|
val hostOriginal = Host.parse(url.toUri().authority ?: "")
|
||||||
|
|
||||||
// 選択肢:ブラウザで表示する
|
// 選択肢:ブラウザで表示する
|
||||||
dialog.addAction(getString(R.string.open_web_on_host, hostOriginal.pretty)) { openCustomTab(url) }
|
dialog.addAction(getString(R.string.open_web_on_host,
|
||||||
|
hostOriginal.pretty)) { openCustomTab(url) }
|
||||||
|
|
||||||
// トゥートの投稿元タンスにあるアカウント
|
// トゥートの投稿元タンスにあるアカウント
|
||||||
val localAccountList = ArrayList<SavedAccount>()
|
val localAccountList = ArrayList<SavedAccount>()
|
||||||
|
@ -263,7 +283,7 @@ fun ActMain.conversationOtherInstance(
|
||||||
) {
|
) {
|
||||||
launchMain {
|
launchMain {
|
||||||
addPseudoAccount(hostOriginal)?.let { sa ->
|
addPseudoAccount(hostOriginal)?.let { sa ->
|
||||||
conversationLocal(pos, sa, statusIdOriginal)
|
conversationLocal(pos, sa, statusIdOriginal, isReference = isReference)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -290,7 +310,7 @@ fun ActMain.conversationOtherInstance(
|
||||||
R.string.open_in_account,
|
R.string.open_in_account,
|
||||||
a.acct
|
a.acct
|
||||||
)
|
)
|
||||||
) { conversationLocal(pos, a, statusIdOriginal) }
|
) { conversationLocal(pos, a, statusIdOriginal, isReference = isReference) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,7 +324,7 @@ fun ActMain.conversationOtherInstance(
|
||||||
R.string.open_in_account,
|
R.string.open_in_account,
|
||||||
a.acct
|
a.acct
|
||||||
)
|
)
|
||||||
) { conversationLocal(pos, a, statusIdAccess) }
|
) { conversationLocal(pos, a, statusIdAccess, isReference = isReference) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,8 @@ fun ActMain.handleOtherUri(uri: Uri): Boolean {
|
||||||
statusInfo.url,
|
statusInfo.url,
|
||||||
statusInfo.statusId,
|
statusInfo.statusId,
|
||||||
statusInfo.host,
|
statusInfo.host,
|
||||||
statusInfo.statusId
|
statusInfo.statusId,
|
||||||
|
isReference = statusInfo.isReference,
|
||||||
)
|
)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,8 +40,6 @@ object ApiPath {
|
||||||
|
|
||||||
// リストではなくオブジェクトを返すAPI
|
// リストではなくオブジェクトを返すAPI
|
||||||
const val PATH_STATUSES = "/api/v1/statuses/%s" // 1:status_id
|
const val PATH_STATUSES = "/api/v1/statuses/%s" // 1:status_id
|
||||||
const val PATH_STATUSES_CONTEXT = "/api/v1/statuses/%s/context" // 1:status_id
|
|
||||||
// search args 1: query(urlencoded) , also, append "&resolve=1" if resolve non-local accounts
|
|
||||||
|
|
||||||
const val PATH_FILTERS = "/api/v1/filters"
|
const val PATH_FILTERS = "/api/v1/filters"
|
||||||
|
|
||||||
|
|
|
@ -4,14 +4,16 @@ import jp.juggler.subwaytooter.api.TootParser
|
||||||
import jp.juggler.util.JsonObject
|
import jp.juggler.util.JsonObject
|
||||||
|
|
||||||
class TootContext(
|
class TootContext(
|
||||||
// The ancestors of the status in the conversation, as a list of Statuses
|
// The ancestors of the status in the conversation, as a list of Statuses
|
||||||
val ancestors: ArrayList<TootStatus>?,
|
val ancestors: ArrayList<TootStatus>?,
|
||||||
// descendants The descendants of the status in the conversation, as a list of Statuses
|
// descendants The descendants of the status in the conversation, as a list of Statuses
|
||||||
val descendants: ArrayList<TootStatus>?,
|
val descendants: ArrayList<TootStatus>?,
|
||||||
|
// fedibird: 参照
|
||||||
|
val references: ArrayList<TootStatus>?,
|
||||||
) {
|
) {
|
||||||
constructor(parser: TootParser, src: JsonObject) : this(
|
constructor(parser: TootParser, src: JsonObject) : this(
|
||||||
ancestors = parseListOrNull(::TootStatus, parser, src.jsonArray("ancestors")),
|
ancestors = parseListOrNull(::TootStatus, parser, src.jsonArray("ancestors")),
|
||||||
descendants = parseListOrNull(::TootStatus, parser, src.jsonArray("descendants"))
|
descendants = parseListOrNull(::TootStatus, parser, src.jsonArray("descendants")),
|
||||||
|
references = parseListOrNull(::TootStatus, parser, src.jsonArray("references")),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,12 @@ import jp.juggler.subwaytooter.util.LinkHelper
|
||||||
import jp.juggler.subwaytooter.util.VersionString
|
import jp.juggler.subwaytooter.util.VersionString
|
||||||
import jp.juggler.util.*
|
import jp.juggler.util.*
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.withTimeout
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
|
import kotlin.coroutines.Continuation
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
// インスタンスの種別
|
// インスタンスの種別
|
||||||
|
@ -220,16 +224,9 @@ class TootInstance(parser: TootParser, src: JsonObject) {
|
||||||
}
|
}
|
||||||
|
|
||||||
class Stats(src: JsonObject) {
|
class Stats(src: JsonObject) {
|
||||||
|
val user_count = src.long("user_count") ?: -1L
|
||||||
val user_count: Long
|
val status_count = src.long("status_count") ?: -1L
|
||||||
val status_count: Long
|
val domain_count = src.long("domain_count") ?: -1L
|
||||||
val domain_count: Long
|
|
||||||
|
|
||||||
init {
|
|
||||||
this.user_count = src.long("user_count") ?: -1L
|
|
||||||
this.status_count = src.long("status_count") ?: -1L
|
|
||||||
this.domain_count = src.long("domain_count") ?: -1L
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val misskeyVersion: Int
|
val misskeyVersion: Int
|
||||||
|
@ -239,6 +236,9 @@ class TootInstance(parser: TootParser, src: JsonObject) {
|
||||||
else -> 10
|
else -> 10
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val canUseReference: Boolean?
|
||||||
|
get() = fedibird_capabilities?.contains("status_reference")
|
||||||
|
|
||||||
fun versionGE(check: VersionString): Boolean {
|
fun versionGE(check: VersionString): Boolean {
|
||||||
if (decoded_version.isEmpty || check.isEmpty) return false
|
if (decoded_version.isEmpty || check.isEmpty) return false
|
||||||
val i = VersionString.compare(decoded_version, check)
|
val i = VersionString.compare(decoded_version, check)
|
||||||
|
@ -246,6 +246,7 @@ class TootInstance(parser: TootParser, src: JsonObject) {
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private val log = LogCategory("TootInstance")
|
||||||
|
|
||||||
private val rePleroma = """\bpleroma\b""".asciiPattern(Pattern.CASE_INSENSITIVE)
|
private val rePleroma = """\bpleroma\b""".asciiPattern(Pattern.CASE_INSENSITIVE)
|
||||||
private val rePixelfed = """\bpixelfed\b""".asciiPattern(Pattern.CASE_INSENSITIVE)
|
private val rePixelfed = """\bpixelfed\b""".asciiPattern(Pattern.CASE_INSENSITIVE)
|
||||||
|
@ -375,26 +376,33 @@ class TootInstance(parser: TootParser, src: JsonObject) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// マストドンのインスタンス情報を読めたら、それはマストドンのインスタンス
|
// マストドンのインスタンス情報を読めたら、それはマストドンのインスタンス
|
||||||
val r1 = getInstanceInformationMastodon(forceAccessToken) ?: return null
|
// インスタンス情報を読めない場合もホワイトリストモードの問題があるので
|
||||||
if (r1.jsonObject != null) return r1
|
// マストドン側のエラーを返す
|
||||||
|
return getInstanceInformationMastodon(forceAccessToken)
|
||||||
return r1 // ホワイトリストモードの問題があるのでマストドン側のエラーを返す
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TootInstance.get() のエラー戻り値を作る
|
||||||
|
*/
|
||||||
|
private fun tiError(errMsg: String) =
|
||||||
|
Pair<TootInstance?, TootApiResult?>(null, TootApiResult(errMsg))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* サーバ情報リクエスト
|
||||||
|
* - ホスト別のキューで実行する
|
||||||
|
*/
|
||||||
class QueuedRequest(
|
class QueuedRequest(
|
||||||
|
var cont: Continuation<Pair<TootInstance?, TootApiResult?>>,
|
||||||
val allowPixelfed: Boolean,
|
val allowPixelfed: Boolean,
|
||||||
val get: suspend (cached: TootInstance?) -> Pair<TootInstance?, TootApiResult?>,
|
val get: suspend (cached: TootInstance?) -> Pair<TootInstance?, TootApiResult?>,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ホスト別のインスタンス情報キャッシュと処理キュー
|
||||||
|
*/
|
||||||
|
class CacheEntry(
|
||||||
|
val hostLower: String,
|
||||||
) {
|
) {
|
||||||
val result = Channel<Pair<TootInstance?, TootApiResult?>>()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun queuedRequest(
|
|
||||||
allowPixelfed: Boolean,
|
|
||||||
get: suspend (cached: TootInstance?) -> Pair<TootInstance?, TootApiResult?>,
|
|
||||||
) = QueuedRequest(allowPixelfed, get)
|
|
||||||
|
|
||||||
// インスタンス情報のキャッシュ。同期オブジェクトを兼ねる
|
|
||||||
class CacheEntry {
|
|
||||||
// インスタンス情報のキャッシュ
|
// インスタンス情報のキャッシュ
|
||||||
var cacheData: TootInstance? = null
|
var cacheData: TootInstance? = null
|
||||||
|
|
||||||
|
@ -402,32 +410,38 @@ class TootInstance(parser: TootParser, src: JsonObject) {
|
||||||
val requestQueue = Channel<QueuedRequest>(capacity = Channel.UNLIMITED)
|
val requestQueue = Channel<QueuedRequest>(capacity = Channel.UNLIMITED)
|
||||||
|
|
||||||
private suspend fun handleRequest(req: QueuedRequest) = try {
|
private suspend fun handleRequest(req: QueuedRequest) = try {
|
||||||
val pair = req.get(cacheData)
|
val qrr = req.get(cacheData)
|
||||||
|
qrr.first?.let { cacheData = it }
|
||||||
pair.first?.let { cacheData = it }
|
|
||||||
|
|
||||||
when {
|
when {
|
||||||
pair.first?.instanceType == InstanceType.Pixelfed &&
|
qrr.first?.instanceType == InstanceType.Pixelfed &&
|
||||||
!PrefB.bpEnablePixelfed() &&
|
!PrefB.bpEnablePixelfed() &&
|
||||||
!req.allowPixelfed ->
|
!req.allowPixelfed ->
|
||||||
Pair(
|
tiError("currently Pixelfed instance is not supported.")
|
||||||
null, TootApiResult("currently Pixelfed instance is not supported.")
|
else -> qrr
|
||||||
)
|
|
||||||
|
|
||||||
else -> pair
|
|
||||||
}
|
}
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
Pair(
|
log.e(ex, "handleRequest failed.")
|
||||||
null,
|
tiError(ex.withCaption("can't get server information."))
|
||||||
TootApiResult(ex.withCaption("can't get server information."))
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
launchDefault {
|
launchDefault {
|
||||||
while (true) {
|
while (true) {
|
||||||
for (req in requestQueue) {
|
try {
|
||||||
req.result.send(handleRequest(req))
|
val req = requestQueue.receive()
|
||||||
|
val r = try {
|
||||||
|
withTimeout(30000L) {
|
||||||
|
handleRequest(req)
|
||||||
|
}
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
log.trace(ex, "handleRequest failed.")
|
||||||
|
tiError(ex.withCaption("handleRequest failed."))
|
||||||
|
}
|
||||||
|
runCatching { req.cont.resumeWith(Result.success(r)) }
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
log.trace(ex, "requestQueue.take failed.")
|
||||||
|
delay(3000L)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -441,7 +455,7 @@ class TootInstance(parser: TootParser, src: JsonObject) {
|
||||||
val hostLower = ascii.lowercase()
|
val hostLower = ascii.lowercase()
|
||||||
var item = _hostCache[hostLower]
|
var item = _hostCache[hostLower]
|
||||||
if (item == null) {
|
if (item == null) {
|
||||||
item = CacheEntry()
|
item = CacheEntry(hostLower)
|
||||||
_hostCache[hostLower] = item
|
_hostCache[hostLower] = item
|
||||||
}
|
}
|
||||||
item
|
item
|
||||||
|
@ -463,86 +477,99 @@ class TootInstance(parser: TootParser, src: JsonObject) {
|
||||||
forceUpdate: Boolean = false,
|
forceUpdate: Boolean = false,
|
||||||
forceAccessToken: String? = null, // マストドンのwhitelist modeでアカウント追加時に必要
|
forceAccessToken: String? = null, // マストドンのwhitelist modeでアカウント追加時に必要
|
||||||
): Pair<TootInstance?, TootApiResult?> {
|
): Pair<TootInstance?, TootApiResult?> {
|
||||||
|
try {
|
||||||
|
val cacheEntry = (hostArg ?: account?.apiHost ?: client.apiHost)?.getCacheEntry()
|
||||||
|
?: return tiError("missing host.")
|
||||||
|
|
||||||
val cacheEntry = (hostArg ?: account?.apiHost ?: client.apiHost)?.getCacheEntry()
|
return withTimeout(30000L) {
|
||||||
?: return Pair(null, TootApiResult("missing host."))
|
suspendCoroutine { cont ->
|
||||||
|
QueuedRequest(cont, allowPixelfed) { cached ->
|
||||||
|
|
||||||
// ホスト名ごとに用意したオブジェクトで同期する
|
// may use cached item.
|
||||||
return queuedRequest(allowPixelfed) { cached ->
|
if (!forceUpdate && forceAccessToken == null && cached != null) {
|
||||||
|
val now = SystemClock.elapsedRealtime()
|
||||||
|
if (now - cached.time_parse <= EXPIRE) {
|
||||||
|
return@QueuedRequest Pair(cached, TootApiResult())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// may use cached item.
|
val tmpInstance = client.apiHost
|
||||||
if (!forceUpdate && forceAccessToken == null && cached != null) {
|
val tmpAccount = client.account
|
||||||
val now = SystemClock.elapsedRealtime()
|
|
||||||
if (now - cached.time_parse <= EXPIRE) {
|
|
||||||
return@queuedRequest Pair(cached, TootApiResult())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val tmpInstance = client.apiHost
|
val linkHelper: LinkHelper?
|
||||||
val tmpAccount = client.account
|
|
||||||
|
|
||||||
val linkHelper: LinkHelper?
|
// get new information
|
||||||
|
val result = when {
|
||||||
|
|
||||||
// get new information
|
// ストリームマネジャから呼ばれる
|
||||||
val result = when {
|
account != null -> try {
|
||||||
|
linkHelper = account
|
||||||
|
client.account = account // this may change client.apiHost
|
||||||
|
if (account.isMisskey) {
|
||||||
|
client.getInstanceInformationMisskey()
|
||||||
|
} else {
|
||||||
|
client.getInstanceInformationMastodon()
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
client.account = tmpAccount
|
||||||
|
client.apiHost = tmpInstance // must be last.
|
||||||
|
}
|
||||||
|
|
||||||
// ストリームマネジャから呼ばれる
|
// サーバ情報カラムやProfileDirectoryを開く場合
|
||||||
account != null -> try {
|
hostArg != null && hostArg != tmpInstance -> try {
|
||||||
linkHelper = account
|
linkHelper = null
|
||||||
client.account = account // this may change client.apiHost
|
client.account = null // don't use access token.
|
||||||
if (account.isMisskey) {
|
client.apiHost = hostArg
|
||||||
client.getInstanceInformationMisskey()
|
client.getInstanceInformation()
|
||||||
} else {
|
} finally {
|
||||||
client.getInstanceInformationMastodon()
|
client.account = tmpAccount
|
||||||
|
client.apiHost = tmpInstance // must be last.
|
||||||
|
}
|
||||||
|
|
||||||
|
// client にすでにあるアクセス情報でサーバ情報を取得する
|
||||||
|
// マストドンのホワイトリストモード用にアクセストークンを指定できる
|
||||||
|
else -> {
|
||||||
|
linkHelper = client.account // may null
|
||||||
|
client.getInstanceInformation(
|
||||||
|
forceAccessToken = forceAccessToken
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val json = result?.jsonObject
|
||||||
|
?: return@QueuedRequest Pair(null, result)
|
||||||
|
|
||||||
|
val item = parseItem(
|
||||||
|
::TootInstance,
|
||||||
|
TootParser(
|
||||||
|
client.context,
|
||||||
|
linkHelper = linkHelper ?: LinkHelper.create(
|
||||||
|
(hostArg ?: client.apiHost)!!,
|
||||||
|
misskeyVersion = parseMisskeyVersion(json)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
json
|
||||||
|
) ?: return@QueuedRequest Pair(
|
||||||
|
null,
|
||||||
|
result.setError("instance information parse error.")
|
||||||
|
)
|
||||||
|
|
||||||
|
Pair(item, result)
|
||||||
|
}.let {
|
||||||
|
val result = cacheEntry.requestQueue.trySend(it)
|
||||||
|
when {
|
||||||
|
// 誰も閉じないので発生しない
|
||||||
|
result.isClosed -> error("cacheEntry.requestQueue closed")
|
||||||
|
// capacity=UNLIMITEDなので発生しない
|
||||||
|
result.isFailure -> error("cacheEntry.requestQueue failed")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
client.account = tmpAccount
|
|
||||||
client.apiHost = tmpInstance // must be last.
|
|
||||||
}
|
|
||||||
|
|
||||||
// サーバ情報カラムやProfileDirectoryを開く場合
|
|
||||||
hostArg != null && hostArg != tmpInstance -> try {
|
|
||||||
linkHelper = null
|
|
||||||
client.account = null // don't use access token.
|
|
||||||
client.apiHost = hostArg
|
|
||||||
client.getInstanceInformation()
|
|
||||||
} finally {
|
|
||||||
client.account = tmpAccount
|
|
||||||
client.apiHost = tmpInstance // must be last.
|
|
||||||
}
|
|
||||||
|
|
||||||
// client にすでにあるアクセス情報でサーバ情報を取得する
|
|
||||||
// マストドンのホワイトリストモード用にアクセストークンを指定できる
|
|
||||||
else -> {
|
|
||||||
linkHelper = client.account // may null
|
|
||||||
client.getInstanceInformation(
|
|
||||||
forceAccessToken = forceAccessToken
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (ex: Throwable) {
|
||||||
val json = result?.jsonObject
|
log.w(ex, "getEx failed.")
|
||||||
?: return@queuedRequest Pair(null, result)
|
return tiError(ex.withCaption("can't get instance information"))
|
||||||
|
|
||||||
val item = parseItem(
|
|
||||||
::TootInstance,
|
|
||||||
TootParser(
|
|
||||||
client.context,
|
|
||||||
linkHelper = linkHelper ?: LinkHelper.create(
|
|
||||||
(hostArg ?: client.apiHost)!!,
|
|
||||||
misskeyVersion = parseMisskeyVersion(json)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
json
|
|
||||||
) ?: return@queuedRequest Pair(
|
|
||||||
null,
|
|
||||||
result.setError("instance information parse error.")
|
|
||||||
)
|
|
||||||
|
|
||||||
Pair(item, result)
|
|
||||||
}
|
}
|
||||||
.also { cacheEntry.requestQueue.send(it) }
|
|
||||||
.result.receive()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,26 +2,25 @@ package jp.juggler.subwaytooter.api.entity
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
|
import androidx.annotation.StringRes
|
||||||
import jp.juggler.subwaytooter.App1
|
import jp.juggler.subwaytooter.App1
|
||||||
import jp.juggler.subwaytooter.pref.PrefB
|
|
||||||
import jp.juggler.subwaytooter.R
|
import jp.juggler.subwaytooter.R
|
||||||
import jp.juggler.subwaytooter.api.*
|
import jp.juggler.subwaytooter.api.TootAccountMap
|
||||||
|
import jp.juggler.subwaytooter.api.TootParser
|
||||||
import jp.juggler.subwaytooter.emoji.CustomEmoji
|
import jp.juggler.subwaytooter.emoji.CustomEmoji
|
||||||
import jp.juggler.subwaytooter.mfm.SpannableStringBuilderEx
|
import jp.juggler.subwaytooter.mfm.SpannableStringBuilderEx
|
||||||
|
import jp.juggler.subwaytooter.pref.PrefB
|
||||||
import jp.juggler.subwaytooter.table.HighlightWord
|
import jp.juggler.subwaytooter.table.HighlightWord
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
import jp.juggler.subwaytooter.table.SavedAccount
|
||||||
import jp.juggler.subwaytooter.util.*
|
import jp.juggler.subwaytooter.util.DecodeOptions
|
||||||
|
import jp.juggler.subwaytooter.util.HTMLDecoder
|
||||||
import jp.juggler.util.*
|
import jp.juggler.util.*
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
import kotlin.collections.ArrayList
|
|
||||||
import kotlin.collections.HashMap
|
|
||||||
import kotlin.jvm.Throws
|
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
@ -1079,8 +1078,8 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
|
||||||
val statusId: EntityId?, // may null
|
val statusId: EntityId?, // may null
|
||||||
hostArg: String,
|
hostArg: String,
|
||||||
val url: String,
|
val url: String,
|
||||||
|
val isReference: Boolean = false,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val host = Host.parse(hostArg)
|
val host = Host.parse(hostArg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1117,6 +1116,11 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
|
||||||
private val reStatusPage = """\Ahttps://([^/]+)/@(\w+)/([^?#/\s]+)(?:\z|[?#])"""
|
private val reStatusPage = """\Ahttps://([^/]+)/@(\w+)/([^?#/\s]+)(?:\z|[?#])"""
|
||||||
.asciiPattern()
|
.asciiPattern()
|
||||||
|
|
||||||
|
// fedibird ステータスの参照のURL
|
||||||
|
private val reStatusWithReference =
|
||||||
|
"""\Ahttps://([^/]+)/@(\w+)/([^?#/\s]+)/references(?:\z|[?#])"""
|
||||||
|
.asciiPattern()
|
||||||
|
|
||||||
// 公開ステータスページのURL Misskey
|
// 公開ステータスページのURL Misskey
|
||||||
internal val reStatusPageMisskey =
|
internal val reStatusPageMisskey =
|
||||||
"""\Ahttps://([^/]+)/notes/([0-9a-f]{24}|[0-9a-z]{10})\b"""
|
"""\Ahttps://([^/]+)/notes/([0-9a-f]{24}|[0-9a-z]{10})\b"""
|
||||||
|
@ -1138,8 +1142,20 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
|
||||||
|
|
||||||
// returns null or pair( status_id, host ,url )
|
// returns null or pair( status_id, host ,url )
|
||||||
fun String.findStatusIdFromUrl(): FindStatusIdFromUrlResult? {
|
fun String.findStatusIdFromUrl(): FindStatusIdFromUrlResult? {
|
||||||
|
|
||||||
|
// https://fedibird.com/@noellabo/108730353756004469/references
|
||||||
|
var m = reStatusWithReference.matcher(this)
|
||||||
|
if (m.find()) {
|
||||||
|
return FindStatusIdFromUrlResult(
|
||||||
|
EntityId(m.groupEx(3)!!),
|
||||||
|
m.groupEx(1)!!,
|
||||||
|
this,
|
||||||
|
isReference = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// https://mastodon.juggler.jp/@SubwayTooter/(status_id)
|
// https://mastodon.juggler.jp/@SubwayTooter/(status_id)
|
||||||
var m = reStatusPage.matcher(this)
|
m = reStatusPage.matcher(this)
|
||||||
if (m.find()) {
|
if (m.find()) {
|
||||||
return FindStatusIdFromUrlResult(EntityId(m.groupEx(3)!!), m.groupEx(1)!!, this)
|
return FindStatusIdFromUrlResult(EntityId(m.groupEx(3)!!), m.groupEx(1)!!, this)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import jp.juggler.util.JsonObject
|
||||||
import jp.juggler.util.encodeBase64Url
|
import jp.juggler.util.encodeBase64Url
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
// カラムデータのJSONエンコーダ、デコーダ
|
// カラムデータのJSONエンコーダ、デコーダ
|
||||||
|
|
||||||
|
@ -164,6 +163,7 @@ object ColumnEncoder {
|
||||||
when (type) {
|
when (type) {
|
||||||
|
|
||||||
ColumnType.CONVERSATION,
|
ColumnType.CONVERSATION,
|
||||||
|
ColumnType.CONVERSATION_WITH_REFERENCE,
|
||||||
ColumnType.BOOSTED_BY,
|
ColumnType.BOOSTED_BY,
|
||||||
ColumnType.FAVOURITED_BY,
|
ColumnType.FAVOURITED_BY,
|
||||||
ColumnType.LOCAL_AROUND,
|
ColumnType.LOCAL_AROUND,
|
||||||
|
@ -299,6 +299,7 @@ object ColumnEncoder {
|
||||||
when (type) {
|
when (type) {
|
||||||
|
|
||||||
ColumnType.CONVERSATION,
|
ColumnType.CONVERSATION,
|
||||||
|
ColumnType.CONVERSATION_WITH_REFERENCE,
|
||||||
ColumnType.BOOSTED_BY,
|
ColumnType.BOOSTED_BY,
|
||||||
ColumnType.FAVOURITED_BY,
|
ColumnType.FAVOURITED_BY,
|
||||||
ColumnType.LOCAL_AROUND,
|
ColumnType.LOCAL_AROUND,
|
||||||
|
|
|
@ -2,7 +2,7 @@ package jp.juggler.subwaytooter.column
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import jp.juggler.subwaytooter.*
|
import jp.juggler.subwaytooter.R
|
||||||
import jp.juggler.subwaytooter.api.entity.EntityId
|
import jp.juggler.subwaytooter.api.entity.EntityId
|
||||||
import jp.juggler.subwaytooter.api.entity.TimelineItem
|
import jp.juggler.subwaytooter.api.entity.TimelineItem
|
||||||
import jp.juggler.subwaytooter.api.entity.TootStatus
|
import jp.juggler.subwaytooter.api.entity.TootStatus
|
||||||
|
@ -32,6 +32,7 @@ fun Column.canReloadWhenRefreshTop(): Boolean = when (type) {
|
||||||
ColumnType.SEARCH_TS,
|
ColumnType.SEARCH_TS,
|
||||||
ColumnType.SEARCH_NOTESTOCK,
|
ColumnType.SEARCH_NOTESTOCK,
|
||||||
ColumnType.CONVERSATION,
|
ColumnType.CONVERSATION,
|
||||||
|
ColumnType.CONVERSATION_WITH_REFERENCE,
|
||||||
ColumnType.LIST_LIST,
|
ColumnType.LIST_LIST,
|
||||||
ColumnType.TREND_TAG,
|
ColumnType.TREND_TAG,
|
||||||
ColumnType.FOLLOW_SUGGESTION,
|
ColumnType.FOLLOW_SUGGESTION,
|
||||||
|
@ -52,6 +53,7 @@ fun Column.canRefreshTopBySwipe(): Boolean =
|
||||||
canReloadWhenRefreshTop() ||
|
canReloadWhenRefreshTop() ||
|
||||||
when (type) {
|
when (type) {
|
||||||
ColumnType.CONVERSATION,
|
ColumnType.CONVERSATION,
|
||||||
|
ColumnType.CONVERSATION_WITH_REFERENCE,
|
||||||
ColumnType.INSTANCE_INFORMATION,
|
ColumnType.INSTANCE_INFORMATION,
|
||||||
-> false
|
-> false
|
||||||
else -> true
|
else -> true
|
||||||
|
@ -61,6 +63,7 @@ fun Column.canRefreshTopBySwipe(): Boolean =
|
||||||
fun Column.canRefreshBottomBySwipe(): Boolean = when (type) {
|
fun Column.canRefreshBottomBySwipe(): Boolean = when (type) {
|
||||||
ColumnType.LIST_LIST,
|
ColumnType.LIST_LIST,
|
||||||
ColumnType.CONVERSATION,
|
ColumnType.CONVERSATION,
|
||||||
|
ColumnType.CONVERSATION_WITH_REFERENCE,
|
||||||
ColumnType.INSTANCE_INFORMATION,
|
ColumnType.INSTANCE_INFORMATION,
|
||||||
ColumnType.KEYWORD_FILTER,
|
ColumnType.KEYWORD_FILTER,
|
||||||
ColumnType.SEARCH,
|
ColumnType.SEARCH,
|
||||||
|
@ -335,7 +338,9 @@ fun Column.startRefreshForPost(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnType.CONVERSATION -> {
|
ColumnType.CONVERSATION,
|
||||||
|
ColumnType.CONVERSATION_WITH_REFERENCE,
|
||||||
|
-> {
|
||||||
// 会話への返信が行われたなら会話を更新する
|
// 会話への返信が行われたなら会話を更新する
|
||||||
try {
|
try {
|
||||||
if (postedReplyId != null) {
|
if (postedReplyId != null) {
|
||||||
|
|
|
@ -3,7 +3,7 @@ package jp.juggler.subwaytooter.column
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import androidx.annotation.RawRes
|
import androidx.annotation.RawRes
|
||||||
import jp.juggler.subwaytooter.*
|
import jp.juggler.subwaytooter.R
|
||||||
import jp.juggler.subwaytooter.api.TootApiClient
|
import jp.juggler.subwaytooter.api.TootApiClient
|
||||||
import jp.juggler.subwaytooter.api.TootApiResult
|
import jp.juggler.subwaytooter.api.TootApiResult
|
||||||
import jp.juggler.subwaytooter.api.TootParser
|
import jp.juggler.subwaytooter.api.TootParser
|
||||||
|
@ -11,7 +11,6 @@ import jp.juggler.subwaytooter.api.entity.*
|
||||||
import jp.juggler.subwaytooter.columnviewholder.saveScrollPosition
|
import jp.juggler.subwaytooter.columnviewholder.saveScrollPosition
|
||||||
import jp.juggler.util.*
|
import jp.juggler.util.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
private val log = LogCategory("ColumnExtra2")
|
private val log = LogCategory("ColumnExtra2")
|
||||||
|
|
||||||
|
@ -61,6 +60,14 @@ val Column.isPublicStream: Boolean
|
||||||
fun Column.canAutoRefresh() =
|
fun Column.canAutoRefresh() =
|
||||||
!accessInfo.isNA && type.canAutoRefresh
|
!accessInfo.isNA && type.canAutoRefresh
|
||||||
|
|
||||||
|
val Column.isConversation
|
||||||
|
get() = when (type) {
|
||||||
|
ColumnType.CONVERSATION,
|
||||||
|
ColumnType.CONVERSATION_WITH_REFERENCE,
|
||||||
|
-> true
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
// 読み込み処理の内部で使うメソッド
|
// 読み込み処理の内部で使うメソッド
|
||||||
|
|
||||||
|
@ -116,7 +123,11 @@ fun Column.getNotificationTypeString(): String {
|
||||||
return sb.toString()
|
return sb.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun Column.loadProfileAccount(client: TootApiClient, parser: TootParser, bForceReload: Boolean): TootApiResult? =
|
suspend fun Column.loadProfileAccount(
|
||||||
|
client: TootApiClient,
|
||||||
|
parser: TootParser,
|
||||||
|
bForceReload: Boolean,
|
||||||
|
): TootApiResult? =
|
||||||
when {
|
when {
|
||||||
// リロード不要なら何もしない
|
// リロード不要なら何もしない
|
||||||
this.whoAccount != null && !bForceReload -> null
|
this.whoAccount != null && !bForceReload -> null
|
||||||
|
|
|
@ -34,11 +34,18 @@ val Column.isFilterEnabled: Boolean
|
||||||
// マストドン2.4.3rcのキーワードフィルタのコンテキスト
|
// マストドン2.4.3rcのキーワードフィルタのコンテキスト
|
||||||
fun Column.getFilterContext() = when (type) {
|
fun Column.getFilterContext() = when (type) {
|
||||||
|
|
||||||
ColumnType.HOME, ColumnType.LIST_TL, ColumnType.MISSKEY_HYBRID -> TootFilter.CONTEXT_HOME
|
ColumnType.HOME,
|
||||||
|
ColumnType.LIST_TL,
|
||||||
|
ColumnType.MISSKEY_HYBRID,
|
||||||
|
-> TootFilter.CONTEXT_HOME
|
||||||
|
|
||||||
ColumnType.NOTIFICATIONS, ColumnType.NOTIFICATION_FROM_ACCT -> TootFilter.CONTEXT_NOTIFICATIONS
|
ColumnType.NOTIFICATIONS,
|
||||||
|
ColumnType.NOTIFICATION_FROM_ACCT,
|
||||||
|
-> TootFilter.CONTEXT_NOTIFICATIONS
|
||||||
|
|
||||||
ColumnType.CONVERSATION -> TootFilter.CONTEXT_THREAD
|
ColumnType.CONVERSATION,
|
||||||
|
ColumnType.CONVERSATION_WITH_REFERENCE,
|
||||||
|
-> TootFilter.CONTEXT_THREAD
|
||||||
|
|
||||||
ColumnType.DIRECT_MESSAGES -> TootFilter.CONTEXT_THREAD
|
ColumnType.DIRECT_MESSAGES -> TootFilter.CONTEXT_THREAD
|
||||||
|
|
||||||
|
@ -76,7 +83,10 @@ fun Column.canFilterBoost(): Boolean = when (type) {
|
||||||
-> true
|
-> true
|
||||||
ColumnType.LOCAL, ColumnType.FEDERATE, ColumnType.HASHTAG, ColumnType.SEARCH -> isMisskey
|
ColumnType.LOCAL, ColumnType.FEDERATE, ColumnType.HASHTAG, ColumnType.SEARCH -> isMisskey
|
||||||
ColumnType.HASHTAG_FROM_ACCT -> false
|
ColumnType.HASHTAG_FROM_ACCT -> false
|
||||||
ColumnType.CONVERSATION, ColumnType.DIRECT_MESSAGES -> isMisskey
|
ColumnType.CONVERSATION,
|
||||||
|
ColumnType.CONVERSATION_WITH_REFERENCE,
|
||||||
|
ColumnType.DIRECT_MESSAGES,
|
||||||
|
-> isMisskey
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,13 +52,13 @@ object ColumnSpec {
|
||||||
when (type) {
|
when (type) {
|
||||||
|
|
||||||
ColumnType.CONVERSATION,
|
ColumnType.CONVERSATION,
|
||||||
|
ColumnType.CONVERSATION_WITH_REFERENCE,
|
||||||
ColumnType.BOOSTED_BY,
|
ColumnType.BOOSTED_BY,
|
||||||
ColumnType.FAVOURITED_BY,
|
ColumnType.FAVOURITED_BY,
|
||||||
ColumnType.LOCAL_AROUND,
|
ColumnType.LOCAL_AROUND,
|
||||||
ColumnType.FEDERATED_AROUND,
|
ColumnType.FEDERATED_AROUND,
|
||||||
ColumnType.ACCOUNT_AROUND,
|
ColumnType.ACCOUNT_AROUND,
|
||||||
->
|
-> statusId = getParamEntityId(params, 0)
|
||||||
statusId = getParamEntityId(params, 0)
|
|
||||||
|
|
||||||
ColumnType.STATUS_HISTORY -> {
|
ColumnType.STATUS_HISTORY -> {
|
||||||
statusId = getParamEntityId(params, 0)
|
statusId = getParamEntityId(params, 0)
|
||||||
|
@ -67,8 +67,7 @@ object ColumnSpec {
|
||||||
|
|
||||||
ColumnType.PROFILE, ColumnType.LIST_TL, ColumnType.LIST_MEMBER,
|
ColumnType.PROFILE, ColumnType.LIST_TL, ColumnType.LIST_MEMBER,
|
||||||
ColumnType.MISSKEY_ANTENNA_TL,
|
ColumnType.MISSKEY_ANTENNA_TL,
|
||||||
->
|
-> profileId = getParamEntityId(params, 0)
|
||||||
profileId = getParamEntityId(params, 0)
|
|
||||||
|
|
||||||
ColumnType.HASHTAG ->
|
ColumnType.HASHTAG ->
|
||||||
hashtag = getParamString(params, 0)
|
hashtag = getParamString(params, 0)
|
||||||
|
@ -124,18 +123,17 @@ object ColumnSpec {
|
||||||
ColumnType.LIST_TL,
|
ColumnType.LIST_TL,
|
||||||
ColumnType.LIST_MEMBER,
|
ColumnType.LIST_MEMBER,
|
||||||
ColumnType.MISSKEY_ANTENNA_TL,
|
ColumnType.MISSKEY_ANTENNA_TL,
|
||||||
->
|
-> column.profileId == getParamEntityId(params, 0)
|
||||||
column.profileId == getParamEntityId(params, 0)
|
|
||||||
|
|
||||||
ColumnType.CONVERSATION,
|
ColumnType.CONVERSATION,
|
||||||
|
ColumnType.CONVERSATION_WITH_REFERENCE,
|
||||||
ColumnType.BOOSTED_BY,
|
ColumnType.BOOSTED_BY,
|
||||||
ColumnType.FAVOURITED_BY,
|
ColumnType.FAVOURITED_BY,
|
||||||
ColumnType.LOCAL_AROUND,
|
ColumnType.LOCAL_AROUND,
|
||||||
ColumnType.FEDERATED_AROUND,
|
ColumnType.FEDERATED_AROUND,
|
||||||
ColumnType.ACCOUNT_AROUND,
|
ColumnType.ACCOUNT_AROUND,
|
||||||
ColumnType.STATUS_HISTORY,
|
ColumnType.STATUS_HISTORY,
|
||||||
->
|
-> column.statusId == getParamEntityId(params, 0)
|
||||||
column.statusId == getParamEntityId(params, 0)
|
|
||||||
|
|
||||||
ColumnType.HASHTAG -> {
|
ColumnType.HASHTAG -> {
|
||||||
(getParamString(params, 0) == column.hashtag) &&
|
(getParamString(params, 0) == column.hashtag) &&
|
||||||
|
|
|
@ -1009,7 +1009,10 @@ class ColumnTask_Loading(
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getConversation(client: TootApiClient): TootApiResult? {
|
suspend fun getConversation(
|
||||||
|
client: TootApiClient,
|
||||||
|
withReference: Boolean = false,
|
||||||
|
): TootApiResult? {
|
||||||
return if (isMisskey) {
|
return if (isMisskey) {
|
||||||
// 指定された発言そのもの
|
// 指定された発言そのもの
|
||||||
val queryParams = column.makeMisskeyBaseParameter(parser).apply {
|
val queryParams = column.makeMisskeyBaseParameter(parser).apply {
|
||||||
|
@ -1097,10 +1100,12 @@ class ColumnTask_Loading(
|
||||||
|
|
||||||
// 前後の会話
|
// 前後の会話
|
||||||
result = client.request(
|
result = client.request(
|
||||||
String.format(
|
"/api/v1/statuses/${column.statusId}/context${
|
||||||
Locale.JAPAN,
|
when (withReference) {
|
||||||
ApiPath.PATH_STATUSES_CONTEXT, column.statusId
|
true -> "?with_reference=true"
|
||||||
)
|
else -> ""
|
||||||
|
}
|
||||||
|
}"
|
||||||
)
|
)
|
||||||
jsonObject = result?.jsonObject ?: return result
|
jsonObject = result?.jsonObject ?: return result
|
||||||
val conversationContext =
|
val conversationContext =
|
||||||
|
@ -1116,6 +1121,10 @@ class ColumnTask_Loading(
|
||||||
1
|
1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (conversationContext.references != null) {
|
||||||
|
addWithFilterStatus(this.listTmp, conversationContext.references)
|
||||||
|
}
|
||||||
|
|
||||||
if (conversationContext.ancestors != null) {
|
if (conversationContext.ancestors != null) {
|
||||||
addWithFilterStatus(this.listTmp, conversationContext.ancestors)
|
addWithFilterStatus(this.listTmp, conversationContext.ancestors)
|
||||||
}
|
}
|
||||||
|
|
|
@ -926,8 +926,18 @@ enum class ColumnType(
|
||||||
|
|
||||||
canStreamingMastodon = streamingTypeNo,
|
canStreamingMastodon = streamingTypeNo,
|
||||||
canStreamingMisskey = streamingTypeNo,
|
canStreamingMisskey = streamingTypeNo,
|
||||||
|
),
|
||||||
|
|
||||||
),
|
CONVERSATION_WITH_REFERENCE(
|
||||||
|
47,
|
||||||
|
iconId = { R.drawable.ic_link },
|
||||||
|
name1 = { it.getString(R.string.conversation_with_reference) },
|
||||||
|
name2 = { context.getString(R.string.conversation_with_reference) },
|
||||||
|
loading = { client -> getConversation(client, withReference = true) },
|
||||||
|
|
||||||
|
canStreamingMastodon = streamingTypeNo,
|
||||||
|
canStreamingMisskey = streamingTypeNo,
|
||||||
|
),
|
||||||
|
|
||||||
HASHTAG(
|
HASHTAG(
|
||||||
9,
|
9,
|
||||||
|
@ -2059,9 +2069,6 @@ enum class ColumnType(
|
||||||
|
|
||||||
;
|
;
|
||||||
|
|
||||||
private fun getFollowedHashtags(client: TootApiClient) {
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val old = Column.typeMap[id]
|
val old = Column.typeMap[id]
|
||||||
if (id > 0 && old != null) error("ColumnType: duplicate id $id. name=$name, ${old.name}")
|
if (id > 0 && old != null) error("ColumnType: duplicate id $id. name=$name, ${old.name}")
|
||||||
|
|
|
@ -118,8 +118,7 @@ fun ColumnViewHolder.onPageCreate(column: Column, pageIdx: Int, pageCount: Int)
|
||||||
|
|
||||||
ColumnViewHolder.log.d("onPageCreate [$pageIdx] ${column.getColumnName(true)}")
|
ColumnViewHolder.log.d("onPageCreate [$pageIdx] ${column.getColumnName(true)}")
|
||||||
|
|
||||||
val bSimpleList =
|
val bSimpleList = !column.isConversation && PrefB.bpSimpleList(activity.pref)
|
||||||
column.type != ColumnType.CONVERSATION && PrefB.bpSimpleList(activity.pref)
|
|
||||||
|
|
||||||
tvColumnIndex.text = activity.getString(R.string.column_index, pageIdx + 1, pageCount)
|
tvColumnIndex.text = activity.getString(R.string.column_index, pageIdx + 1, pageCount)
|
||||||
tvColumnStatus.text = "?"
|
tvColumnStatus.text = "?"
|
||||||
|
|
|
@ -3,7 +3,7 @@ package jp.juggler.subwaytooter.itemviewholder
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import jp.juggler.subwaytooter.R
|
import jp.juggler.subwaytooter.R
|
||||||
import jp.juggler.subwaytooter.api.entity.TootStatus
|
import jp.juggler.subwaytooter.api.entity.TootStatus
|
||||||
import jp.juggler.subwaytooter.column.ColumnType
|
import jp.juggler.subwaytooter.column.isConversation
|
||||||
import jp.juggler.subwaytooter.pref.PrefB
|
import jp.juggler.subwaytooter.pref.PrefB
|
||||||
import jp.juggler.subwaytooter.pref.PrefS
|
import jp.juggler.subwaytooter.pref.PrefS
|
||||||
import jp.juggler.subwaytooter.table.MediaShown
|
import jp.juggler.subwaytooter.table.MediaShown
|
||||||
|
@ -53,7 +53,7 @@ fun ItemViewHolder.showPreviewCard(status: TootStatus) {
|
||||||
val card = status.card ?: return
|
val card = status.card ?: return
|
||||||
|
|
||||||
// 会話カラムで返信ステータスなら捏造したカードを表示しない
|
// 会話カラムで返信ステータスなら捏造したカードを表示しない
|
||||||
if (column.type == ColumnType.CONVERSATION &&
|
if (column.isConversation &&
|
||||||
card.originalStatus != null &&
|
card.originalStatus != null &&
|
||||||
status.reply != null
|
status.reply != null
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -648,26 +648,11 @@ fun ItemViewHolder.showStatusTime(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sb.isNotEmpty()) sb.append(' ')
|
if (sb.isNotEmpty()) sb.append(' ')
|
||||||
|
|
||||||
sb.append(
|
sb.append(
|
||||||
when {
|
(time ?: status?.time_created_at)?.let {
|
||||||
time != null -> TootStatus.formatTime(
|
TootStatus.formatTime(activity, it, column.canRelativeTime)
|
||||||
activity,
|
} ?: "?"
|
||||||
time,
|
|
||||||
when (column.type) {
|
|
||||||
ColumnType.CONVERSATION, ColumnType.STATUS_HISTORY -> false
|
|
||||||
else -> true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
status != null -> TootStatus.formatTime(
|
|
||||||
activity,
|
|
||||||
status.time_created_at,
|
|
||||||
when (column.type) {
|
|
||||||
ColumnType.CONVERSATION, ColumnType.STATUS_HISTORY -> false
|
|
||||||
else -> true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
else -> "?"
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
tv.text = sb
|
tv.text = sb
|
||||||
|
@ -703,16 +688,20 @@ fun ItemViewHolder.showStatusTimeScheduled(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sb.isNotEmpty()) sb.append(' ')
|
if (sb.isNotEmpty()) sb.append(' ')
|
||||||
sb.append(
|
sb.append(TootStatus.formatTime(activity, item.timeScheduledAt, column.canRelativeTime))
|
||||||
TootStatus.formatTime(
|
|
||||||
activity,
|
|
||||||
item.timeScheduledAt,
|
|
||||||
column.type != ColumnType.CONVERSATION
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
tv.text = sb
|
tv.text = sb
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val Column.canRelativeTime
|
||||||
|
get() = when (type) {
|
||||||
|
ColumnType.CONVERSATION,
|
||||||
|
ColumnType.CONVERSATION_WITH_REFERENCE,
|
||||||
|
ColumnType.STATUS_HISTORY,
|
||||||
|
-> false
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
|
||||||
// fun updateRelativeTime() {
|
// fun updateRelativeTime() {
|
||||||
// val boost_time = this.boost_time
|
// val boost_time = this.boost_time
|
||||||
// if(boost_time != 0L) {
|
// if(boost_time != 0L) {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import jp.juggler.subwaytooter.actmain.checkAutoCW
|
||||||
import jp.juggler.subwaytooter.api.entity.*
|
import jp.juggler.subwaytooter.api.entity.*
|
||||||
import jp.juggler.subwaytooter.column.Column
|
import jp.juggler.subwaytooter.column.Column
|
||||||
import jp.juggler.subwaytooter.column.ColumnType
|
import jp.juggler.subwaytooter.column.ColumnType
|
||||||
|
import jp.juggler.subwaytooter.column.isConversation
|
||||||
import jp.juggler.subwaytooter.pref.PrefB
|
import jp.juggler.subwaytooter.pref.PrefB
|
||||||
import jp.juggler.subwaytooter.pref.PrefI
|
import jp.juggler.subwaytooter.pref.PrefI
|
||||||
import jp.juggler.subwaytooter.table.ContentWarning
|
import jp.juggler.subwaytooter.table.ContentWarning
|
||||||
|
@ -222,14 +223,14 @@ private fun ItemViewHolder.showApplicationAndLanguage(status: TootStatus) {
|
||||||
|
|
||||||
val application = status.application
|
val application = status.application
|
||||||
if (application != null &&
|
if (application != null &&
|
||||||
(column.type == ColumnType.CONVERSATION || PrefB.bpShowAppName(activity.pref))
|
(column.isConversation || PrefB.bpShowAppName(activity.pref))
|
||||||
) {
|
) {
|
||||||
prepareSb().append(activity.getString(R.string.application_is, application.name ?: ""))
|
prepareSb().append(activity.getString(R.string.application_is, application.name ?: ""))
|
||||||
}
|
}
|
||||||
|
|
||||||
val language = status.language
|
val language = status.language
|
||||||
if (language != null &&
|
if (language != null &&
|
||||||
(column.type == ColumnType.CONVERSATION || PrefB.bpShowLanguage(activity.pref))
|
(column.isConversation || PrefB.bpShowLanguage(activity.pref))
|
||||||
) {
|
) {
|
||||||
prepareSb().append(activity.getString(R.string.language_is, language))
|
prepareSb().append(activity.getString(R.string.language_is, language))
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,9 @@ import android.content.SharedPreferences
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.browser.customtabs.CustomTabColorSchemeParams
|
import androidx.browser.customtabs.CustomTabColorSchemeParams
|
||||||
import androidx.browser.customtabs.CustomTabsIntent
|
import androidx.browser.customtabs.CustomTabsIntent
|
||||||
import jp.juggler.subwaytooter.*
|
import jp.juggler.subwaytooter.ActCallback
|
||||||
|
import jp.juggler.subwaytooter.ActMain
|
||||||
|
import jp.juggler.subwaytooter.R
|
||||||
import jp.juggler.subwaytooter.action.conversationLocal
|
import jp.juggler.subwaytooter.action.conversationLocal
|
||||||
import jp.juggler.subwaytooter.action.conversationOtherInstance
|
import jp.juggler.subwaytooter.action.conversationOtherInstance
|
||||||
import jp.juggler.subwaytooter.action.tagDialog
|
import jp.juggler.subwaytooter.action.tagDialog
|
||||||
|
@ -20,7 +22,6 @@ import jp.juggler.subwaytooter.pref.pref
|
||||||
import jp.juggler.subwaytooter.span.LinkInfo
|
import jp.juggler.subwaytooter.span.LinkInfo
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
import jp.juggler.subwaytooter.table.SavedAccount
|
||||||
import jp.juggler.util.*
|
import jp.juggler.util.*
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
// Subway Tooterの「アプリ設定/挙動/リンクを開く際にCustom Tabsを使わない」をONにして
|
// Subway Tooterの「アプリ設定/挙動/リンクを開く際にCustom Tabsを使わない」をONにして
|
||||||
// 投稿のコンテキストメニューの「トゥートへのアクション/Webページを開く」「ユーザへのアクション/Webページを開く」を使うと
|
// 投稿のコンテキストメニューの「トゥートへのアクション/Webページを開く」「ユーザへのアクション/Webページを開く」を使うと
|
||||||
|
@ -189,7 +190,7 @@ fun openCustomTab(
|
||||||
tagList: ArrayList<String>? = null,
|
tagList: ArrayList<String>? = null,
|
||||||
allowIntercept: Boolean = true,
|
allowIntercept: Boolean = true,
|
||||||
whoRef: TootAccountRef? = null,
|
whoRef: TootAccountRef? = null,
|
||||||
linkInfo: LinkInfo? = null
|
linkInfo: LinkInfo? = null,
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
log.d("openCustomTab: $url")
|
log.d("openCustomTab: $url")
|
||||||
|
@ -219,23 +220,41 @@ fun openCustomTab(
|
||||||
|
|
||||||
val statusInfo = url.findStatusIdFromUrl()
|
val statusInfo = url.findStatusIdFromUrl()
|
||||||
if (statusInfo != null) {
|
if (statusInfo != null) {
|
||||||
if (accessInfo.isNA ||
|
when {
|
||||||
statusInfo.statusId == null ||
|
// fedibirdの参照のURLだった && 閲覧アカウントが参照を扱える
|
||||||
!accessInfo.matchHost(statusInfo.host)
|
// 参照カラムを開く
|
||||||
) {
|
statusInfo.statusId != null &&
|
||||||
activity.conversationOtherInstance(
|
statusInfo.isReference &&
|
||||||
pos,
|
TootInstance.getCached(accessInfo)?.canUseReference == true ->
|
||||||
statusInfo.url,
|
activity.conversationLocal(
|
||||||
statusInfo.statusId,
|
pos,
|
||||||
statusInfo.host,
|
accessInfo,
|
||||||
statusInfo.statusId
|
statusInfo.statusId,
|
||||||
)
|
isReference = statusInfo.isReference,
|
||||||
} else {
|
)
|
||||||
activity.conversationLocal(
|
|
||||||
pos,
|
// 疑似アカウント?
|
||||||
accessInfo,
|
// 別サーバ?
|
||||||
statusInfo.statusId
|
// ステータスIDがない?(Pleroma)
|
||||||
)
|
accessInfo.isNA ||
|
||||||
|
!accessInfo.matchHost(statusInfo.host) ||
|
||||||
|
statusInfo.statusId == null ->
|
||||||
|
activity.conversationOtherInstance(
|
||||||
|
pos,
|
||||||
|
statusInfo.url,
|
||||||
|
statusInfo.statusId,
|
||||||
|
statusInfo.host,
|
||||||
|
statusInfo.statusId,
|
||||||
|
isReference = statusInfo.isReference,
|
||||||
|
)
|
||||||
|
|
||||||
|
else ->
|
||||||
|
activity.conversationLocal(
|
||||||
|
pos,
|
||||||
|
accessInfo,
|
||||||
|
statusInfo.statusId,
|
||||||
|
isReference = statusInfo.isReference,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -248,7 +267,8 @@ fun openCustomTab(
|
||||||
if (fullAcct.host != null) {
|
if (fullAcct.host != null) {
|
||||||
when (fullAcct.host.ascii) {
|
when (fullAcct.host.ascii) {
|
||||||
"github.com",
|
"github.com",
|
||||||
"twitter.com" ->
|
"twitter.com",
|
||||||
|
->
|
||||||
activity.openCustomTab(mention.url)
|
activity.openCustomTab(mention.url)
|
||||||
"gmail.com" ->
|
"gmail.com" ->
|
||||||
activity.openBrowser("mailto:${fullAcct.pretty}")
|
activity.openBrowser("mailto:${fullAcct.pretty}")
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
<vector android:height="24dp" android:tint="#000000"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M17,7h-4v2h4c1.65,0 3,1.35 3,3s-1.35,3 -3,3h-4v2h4c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5zM11,15L7,15c-1.65,0 -3,-1.35 -3,-3s1.35,-3 3,-3h4L11,7L7,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5h4v-2zM8,11h8v2L8,13z"/>
|
||||||
|
</vector>
|
|
@ -1154,4 +1154,5 @@
|
||||||
<string name="followed_tags">フォロー中のハッシュタグ</string>
|
<string name="followed_tags">フォロー中のハッシュタグ</string>
|
||||||
<string name="follow_hashtag_of">\"%1$s\"のフォロー</string>
|
<string name="follow_hashtag_of">\"%1$s\"のフォロー</string>
|
||||||
<string name="unfollow_hashtag_of">\"%1$s\"のフォロー解除</string>
|
<string name="unfollow_hashtag_of">\"%1$s\"のフォロー解除</string>
|
||||||
|
<string name="conversation_with_reference">会話と参照</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -1163,4 +1163,5 @@
|
||||||
<string name="followed_tags">Followed hashtags</string>
|
<string name="followed_tags">Followed hashtags</string>
|
||||||
<string name="follow_hashtag_of">Follow %1$s</string>
|
<string name="follow_hashtag_of">Follow %1$s</string>
|
||||||
<string name="unfollow_hashtag_of">Unfollow %1$s</string>
|
<string name="unfollow_hashtag_of">Unfollow %1$s</string>
|
||||||
|
<string name="conversation_with_reference">conversation + reference</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in New Issue