2021-05-23 14:54:52 +02:00
|
|
|
|
package jp.juggler.subwaytooter.action
|
|
|
|
|
|
2021-05-27 04:15:59 +02:00
|
|
|
|
import android.content.Context
|
2021-06-22 10:31:51 +02:00
|
|
|
|
import jp.juggler.subwaytooter.*
|
2021-05-23 14:54:52 +02:00
|
|
|
|
import jp.juggler.subwaytooter.api.*
|
2021-06-22 10:31:51 +02:00
|
|
|
|
import jp.juggler.subwaytooter.api.entity.*
|
2021-05-23 14:54:52 +02:00
|
|
|
|
import jp.juggler.subwaytooter.dialog.ActionsDialog
|
|
|
|
|
import jp.juggler.subwaytooter.table.AcctColor
|
|
|
|
|
import jp.juggler.subwaytooter.table.SavedAccount
|
|
|
|
|
import jp.juggler.subwaytooter.util.matchHost
|
|
|
|
|
import jp.juggler.subwaytooter.util.openCustomTab
|
|
|
|
|
import jp.juggler.util.*
|
2021-05-27 04:15:59 +02:00
|
|
|
|
import java.util.*
|
2021-05-23 14:54:52 +02:00
|
|
|
|
|
2021-05-27 04:15:59 +02:00
|
|
|
|
private val log = LogCategory("Action_Conversation")
|
2021-05-23 14:54:52 +02:00
|
|
|
|
|
2021-06-22 10:31:51 +02:00
|
|
|
|
fun ActMain.clickConversation(
|
|
|
|
|
pos: Int,
|
|
|
|
|
accessInfo: SavedAccount,
|
|
|
|
|
|
|
|
|
|
// optional. 未読表示のクリアに使う
|
|
|
|
|
listAdapter: ItemListAdapter? = null,
|
|
|
|
|
|
|
|
|
|
// どちらか非nullであること
|
|
|
|
|
status: TootStatus? = null,
|
|
|
|
|
summary: TootConversationSummary? = null,
|
|
|
|
|
) {
|
|
|
|
|
// 未読クリアと表示の更新
|
|
|
|
|
(summary ?: status?.conversationSummary)?.let {
|
|
|
|
|
if (conversationUnreadClear(accessInfo, it)) {
|
|
|
|
|
listAdapter?.notifyChange(
|
|
|
|
|
reason = "ConversationSummary reset unread",
|
|
|
|
|
reset = true
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 会話カラムを開く
|
|
|
|
|
(status ?: summary?.last_status)?.let {
|
|
|
|
|
conversation(pos, accessInfo, it)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// プレビューカードのイメージは返信かもしれない
|
|
|
|
|
fun ActMain.clickCardImage(pos: Int, accessInfo: SavedAccount, card: TootCard?, longClick: Boolean = false) {
|
|
|
|
|
card ?: return
|
|
|
|
|
card.originalStatus?.let {
|
|
|
|
|
if (longClick) {
|
|
|
|
|
conversationOtherInstance(pos, it)
|
|
|
|
|
} else {
|
|
|
|
|
conversation(pos, accessInfo, it)
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
card.url?.notEmpty()?.let {
|
|
|
|
|
openCustomTab(this, pos, it, accessInfo = accessInfo)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 「~からの返信」の表記を押した
|
|
|
|
|
fun ActMain.clickReplyInfo(
|
|
|
|
|
pos: Int,
|
|
|
|
|
accessInfo: SavedAccount,
|
|
|
|
|
columnType: ColumnType,
|
|
|
|
|
statusReply: TootStatus?,
|
|
|
|
|
statusShowing: TootStatus?,
|
|
|
|
|
longClick: Boolean = false,
|
|
|
|
|
contextMenuOpener: ActMain.(status: TootStatus) -> Unit = {},
|
|
|
|
|
) {
|
|
|
|
|
when {
|
|
|
|
|
statusReply != null ->
|
|
|
|
|
if (longClick) {
|
|
|
|
|
contextMenuOpener(this, statusReply)
|
|
|
|
|
} else {
|
|
|
|
|
conversation(pos, accessInfo, statusReply)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// tootsearchは返信元のIDを取得するのにひと手間必要
|
|
|
|
|
columnType == ColumnType.SEARCH_TS ||
|
|
|
|
|
columnType == ColumnType.SEARCH_NOTESTOCK ->
|
|
|
|
|
conversationFromTootsearch(pos, statusShowing)
|
|
|
|
|
|
|
|
|
|
else ->
|
|
|
|
|
statusShowing?.in_reply_to_id
|
|
|
|
|
?.let { conversationLocal(pos, accessInfo, it) }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-27 04:15:59 +02:00
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
// open conversation
|
|
|
|
|
|
|
|
|
|
// returns true if unread flag will be cleared.
|
|
|
|
|
internal fun ActMain.conversationUnreadClear(
|
2021-06-20 15:12:25 +02:00
|
|
|
|
accessInfo: SavedAccount,
|
|
|
|
|
conversationSummary: TootConversationSummary?,
|
2021-05-27 04:15:59 +02:00
|
|
|
|
): Boolean {
|
|
|
|
|
|
|
|
|
|
// サマリがない
|
|
|
|
|
conversationSummary ?: return false
|
|
|
|
|
|
|
|
|
|
// 更新の必要がない
|
|
|
|
|
if (!conversationSummary.unread) return false
|
|
|
|
|
|
|
|
|
|
// 変数を更新
|
|
|
|
|
conversationSummary.unread = false
|
|
|
|
|
|
|
|
|
|
// 未読フラグのクリアをサーバに送る
|
|
|
|
|
launchMain {
|
2021-06-20 15:12:25 +02:00
|
|
|
|
runApiTask(accessInfo, progressStyle = ApiTask.PROGRESS_NONE) { client ->
|
2021-05-27 04:15:59 +02:00
|
|
|
|
client.request(
|
|
|
|
|
"/api/v1/conversations/${conversationSummary.id}/read",
|
|
|
|
|
"".toFormRequestBody().toPost()
|
|
|
|
|
)
|
2021-05-23 14:54:52 +02:00
|
|
|
|
}
|
2021-05-27 04:15:59 +02:00
|
|
|
|
// 応答の内容は見ない
|
2021-05-23 14:54:52 +02:00
|
|
|
|
}
|
|
|
|
|
|
2021-05-27 04:15:59 +02:00
|
|
|
|
return true // 表示も更新されるべき
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ローカルかリモートか判断する
|
|
|
|
|
fun ActMain.conversation(
|
|
|
|
|
pos: Int,
|
2021-06-20 15:12:25 +02:00
|
|
|
|
accessInfo: SavedAccount,
|
|
|
|
|
status: TootStatus,
|
2021-05-27 04:15:59 +02:00
|
|
|
|
) {
|
2021-06-20 15:12:25 +02:00
|
|
|
|
if (accessInfo.isNA || !accessInfo.matchHost(status.readerApDomain)) {
|
2021-05-27 04:15:59 +02:00
|
|
|
|
conversationOtherInstance(pos, status)
|
|
|
|
|
} else {
|
|
|
|
|
|
2021-06-20 15:12:25 +02:00
|
|
|
|
conversationLocal(pos, accessInfo, status.id)
|
2021-05-23 14:54:52 +02:00
|
|
|
|
}
|
2021-05-27 04:15:59 +02:00
|
|
|
|
}
|
2021-05-23 14:54:52 +02:00
|
|
|
|
|
2021-05-27 04:15:59 +02:00
|
|
|
|
// ローカルから見える会話の流れを表示する
|
|
|
|
|
fun ActMain.conversationLocal(
|
|
|
|
|
pos: Int,
|
2021-06-20 15:12:25 +02:00
|
|
|
|
accessInfo: SavedAccount,
|
|
|
|
|
statusId: EntityId,
|
|
|
|
|
) = addColumn(pos, accessInfo, ColumnType.CONVERSATION, statusId)
|
2021-05-23 14:54:52 +02:00
|
|
|
|
|
2021-05-27 04:15:59 +02:00
|
|
|
|
private val reDetailedStatusTime =
|
|
|
|
|
"""<a\b[^>]*?\bdetailed-status__datetime\b[^>]*href="https://[^/]+/@[^/]+/([^\s?#/"]+)"""
|
|
|
|
|
.toRegex()
|
2021-05-23 14:54:52 +02:00
|
|
|
|
|
2021-05-27 04:15:59 +02:00
|
|
|
|
private val reHeaderOgUrl = """<meta\s+content="https://[^/"]+/notice/([^/"]+)"\s+property="og:url"/?>"""
|
|
|
|
|
.toRegex()
|
2021-05-23 14:54:52 +02:00
|
|
|
|
|
2021-05-27 04:15:59 +02:00
|
|
|
|
// 疑似アカウントではURLからIDを取得するのにHTMLと正規表現を使う
|
|
|
|
|
suspend fun guessStatusIdFromPseudoAccount(
|
|
|
|
|
context: Context, // for string resource
|
|
|
|
|
client: TootApiClient, // for get HTML
|
2021-06-20 15:12:25 +02:00
|
|
|
|
remoteStatusUrl: String,
|
2021-05-27 04:15:59 +02:00
|
|
|
|
): Pair<TootApiResult?, EntityId?> {
|
2021-05-23 14:54:52 +02:00
|
|
|
|
|
2021-06-20 15:12:25 +02:00
|
|
|
|
val result = client.getHttp(remoteStatusUrl)
|
2021-05-27 04:15:59 +02:00
|
|
|
|
|
|
|
|
|
result?.string?.let { html ->
|
|
|
|
|
|
|
|
|
|
reDetailedStatusTime.find(html)
|
|
|
|
|
?.groupValues?.elementAtOrNull(1)
|
|
|
|
|
?.let { return Pair(result, EntityId(it)) }
|
|
|
|
|
|
|
|
|
|
reHeaderOgUrl.find(html)
|
|
|
|
|
?.groupValues?.elementAtOrNull(1)
|
|
|
|
|
?.let { return Pair(result, EntityId(it)) }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Pair(
|
|
|
|
|
result?.setError(context.getString(R.string.status_id_conversion_failed)),
|
|
|
|
|
null
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun ActMain.conversationRemote(
|
|
|
|
|
pos: Int,
|
2021-06-20 15:12:25 +02:00
|
|
|
|
accessInfo: SavedAccount,
|
|
|
|
|
remoteStatusUrl: String,
|
2021-05-27 04:15:59 +02:00
|
|
|
|
) {
|
|
|
|
|
|
|
|
|
|
launchMain {
|
2021-06-20 15:12:25 +02:00
|
|
|
|
var localStatusId: EntityId? = null
|
2021-05-27 04:15:59 +02:00
|
|
|
|
|
|
|
|
|
runApiTask(
|
2021-06-20 15:12:25 +02:00
|
|
|
|
accessInfo,
|
2021-05-27 04:15:59 +02:00
|
|
|
|
progressPrefix = getString(R.string.progress_synchronize_toot)
|
|
|
|
|
) { client ->
|
2021-06-20 15:12:25 +02:00
|
|
|
|
if (accessInfo.isPseudo) {
|
2021-05-27 04:15:59 +02:00
|
|
|
|
// 疑似アカウントではURLからIDを取得するのにHTMLと正規表現を使う
|
2021-06-20 15:12:25 +02:00
|
|
|
|
val pair = guessStatusIdFromPseudoAccount(applicationContext, client, remoteStatusUrl)
|
|
|
|
|
localStatusId = pair.second
|
2021-05-27 04:15:59 +02:00
|
|
|
|
pair.first
|
|
|
|
|
} else {
|
|
|
|
|
// 実アカウントでは検索APIを使える
|
2021-06-20 15:12:25 +02:00
|
|
|
|
val (result, status) = client.syncStatus(accessInfo, remoteStatusUrl)
|
2021-05-27 04:15:59 +02:00
|
|
|
|
if (status != null) {
|
2021-06-20 15:12:25 +02:00
|
|
|
|
localStatusId = status.id
|
|
|
|
|
log.d("status id conversion $remoteStatusUrl=>${status.id}")
|
2021-05-27 04:15:59 +02:00
|
|
|
|
}
|
|
|
|
|
result
|
|
|
|
|
}
|
|
|
|
|
}?.let { result ->
|
2021-06-20 15:12:25 +02:00
|
|
|
|
when (val statusId = localStatusId) {
|
2021-05-27 04:15:59 +02:00
|
|
|
|
null -> showToast(true, result.error)
|
2021-06-20 15:12:25 +02:00
|
|
|
|
else -> conversationLocal(pos, accessInfo, statusId)
|
2021-05-23 14:54:52 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-05-27 04:15:59 +02:00
|
|
|
|
}
|
2021-05-23 14:54:52 +02:00
|
|
|
|
|
2021-05-27 04:15:59 +02:00
|
|
|
|
// アプリ外部からURLを渡された場合に呼ばれる
|
|
|
|
|
fun ActMain.conversationOtherInstance(
|
|
|
|
|
pos: Int,
|
|
|
|
|
url: String,
|
2021-06-20 15:12:25 +02:00
|
|
|
|
statusIdOriginal: EntityId? = null,
|
|
|
|
|
hostAccess: Host? = null,
|
|
|
|
|
statusIdAccess: EntityId? = null,
|
2021-05-27 04:15:59 +02:00
|
|
|
|
) {
|
|
|
|
|
val activity = this
|
2021-05-23 14:54:52 +02:00
|
|
|
|
|
2021-05-27 04:15:59 +02:00
|
|
|
|
val dialog = ActionsDialog()
|
2021-05-23 14:54:52 +02:00
|
|
|
|
|
2021-06-20 15:12:25 +02:00
|
|
|
|
val hostOriginal = Host.parse(url.toUri().authority ?: "")
|
2021-05-23 14:54:52 +02:00
|
|
|
|
|
2021-05-27 04:15:59 +02:00
|
|
|
|
// 選択肢:ブラウザで表示する
|
2021-06-20 15:12:25 +02:00
|
|
|
|
dialog.addAction(getString(R.string.open_web_on_host, hostOriginal.pretty)) { openCustomTab(url) }
|
2021-05-23 14:54:52 +02:00
|
|
|
|
|
2021-05-27 04:15:59 +02:00
|
|
|
|
// トゥートの投稿元タンスにあるアカウント
|
2021-06-20 15:12:25 +02:00
|
|
|
|
val localAccountList = ArrayList<SavedAccount>()
|
2021-05-23 14:54:52 +02:00
|
|
|
|
|
2021-05-27 04:15:59 +02:00
|
|
|
|
// TLを読んだタンスにあるアカウント
|
2021-06-20 15:12:25 +02:00
|
|
|
|
val accessAccountList = ArrayList<SavedAccount>()
|
2021-05-23 14:54:52 +02:00
|
|
|
|
|
2021-05-27 04:15:59 +02:00
|
|
|
|
// その他のタンスにあるアカウント
|
2021-06-20 15:12:25 +02:00
|
|
|
|
val otherAccountList = ArrayList<SavedAccount>()
|
2021-05-23 14:54:52 +02:00
|
|
|
|
|
2021-05-27 04:15:59 +02:00
|
|
|
|
for (a in SavedAccount.loadAccountList(applicationContext)) {
|
2021-05-23 14:54:52 +02:00
|
|
|
|
|
2021-05-27 04:15:59 +02:00
|
|
|
|
// 疑似アカウントは後でまとめて処理する
|
|
|
|
|
if (a.isPseudo) continue
|
|
|
|
|
|
2021-06-20 15:12:25 +02:00
|
|
|
|
if (statusIdOriginal != null && a.matchHost(hostOriginal)) {
|
2021-05-27 04:15:59 +02:00
|
|
|
|
// アクセス情報+ステータスID でアクセスできるなら
|
|
|
|
|
// 同タンスのアカウントならステータスIDの変換なしに表示できる
|
2021-06-20 15:12:25 +02:00
|
|
|
|
localAccountList.add(a)
|
|
|
|
|
} else if (statusIdAccess != null && a.matchHost(hostAccess)) {
|
2021-05-27 04:15:59 +02:00
|
|
|
|
// 既に変換済みのステータスIDがあるなら、そのアカウントでもステータスIDの変換は必要ない
|
2021-06-20 15:12:25 +02:00
|
|
|
|
accessAccountList.add(a)
|
2021-05-27 04:15:59 +02:00
|
|
|
|
} else {
|
|
|
|
|
// 別タンスでも実アカウントなら検索APIでステータスIDを変換できる
|
2021-06-20 15:12:25 +02:00
|
|
|
|
otherAccountList.add(a)
|
2021-05-23 14:54:52 +02:00
|
|
|
|
}
|
2021-05-27 04:15:59 +02:00
|
|
|
|
}
|
2021-05-23 14:54:52 +02:00
|
|
|
|
|
2021-05-27 04:15:59 +02:00
|
|
|
|
// 同タンスのアカウントがないなら、疑似アカウントで開く選択肢
|
2021-06-20 15:12:25 +02:00
|
|
|
|
if (localAccountList.isEmpty()) {
|
|
|
|
|
if (statusIdOriginal != null) {
|
2021-05-27 04:15:59 +02:00
|
|
|
|
dialog.addAction(
|
2021-06-20 15:12:25 +02:00
|
|
|
|
getString(R.string.open_in_pseudo_account, "?@${hostOriginal.pretty}")
|
2021-05-27 04:15:59 +02:00
|
|
|
|
) {
|
|
|
|
|
launchMain {
|
2021-06-20 15:12:25 +02:00
|
|
|
|
addPseudoAccount(hostOriginal)?.let { sa ->
|
|
|
|
|
conversationLocal(pos, sa, statusIdOriginal)
|
2021-05-23 14:54:52 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-05-27 04:15:59 +02:00
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
dialog.addAction(
|
2021-06-20 15:12:25 +02:00
|
|
|
|
getString(R.string.open_in_pseudo_account, "?@${hostOriginal.pretty}")
|
2021-05-27 04:15:59 +02:00
|
|
|
|
) {
|
|
|
|
|
launchMain {
|
2021-06-20 15:12:25 +02:00
|
|
|
|
addPseudoAccount(hostOriginal)?.let { sa ->
|
2021-05-27 04:15:59 +02:00
|
|
|
|
conversationRemote(pos, sa, url)
|
2021-05-23 14:54:52 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-05-27 04:15:59 +02:00
|
|
|
|
}
|
2021-05-23 14:54:52 +02:00
|
|
|
|
|
2021-05-27 04:15:59 +02:00
|
|
|
|
// ローカルアカウント
|
2021-06-20 15:12:25 +02:00
|
|
|
|
if (statusIdOriginal != null) {
|
|
|
|
|
SavedAccount.sort(localAccountList)
|
|
|
|
|
for (a in localAccountList) {
|
2021-05-27 04:15:59 +02:00
|
|
|
|
dialog.addAction(
|
|
|
|
|
AcctColor.getStringWithNickname(
|
|
|
|
|
activity,
|
|
|
|
|
R.string.open_in_account,
|
|
|
|
|
a.acct
|
|
|
|
|
)
|
2021-06-20 15:12:25 +02:00
|
|
|
|
) { conversationLocal(pos, a, statusIdOriginal) }
|
2021-05-23 14:54:52 +02:00
|
|
|
|
}
|
2021-05-27 04:15:59 +02:00
|
|
|
|
}
|
2021-05-23 14:54:52 +02:00
|
|
|
|
|
2021-05-27 04:15:59 +02:00
|
|
|
|
// アクセスしたアカウント
|
2021-06-20 15:12:25 +02:00
|
|
|
|
if (statusIdAccess != null) {
|
|
|
|
|
SavedAccount.sort(accessAccountList)
|
|
|
|
|
for (a in accessAccountList) {
|
2021-05-23 14:54:52 +02:00
|
|
|
|
dialog.addAction(
|
|
|
|
|
AcctColor.getStringWithNickname(
|
|
|
|
|
activity,
|
|
|
|
|
R.string.open_in_account,
|
|
|
|
|
a.acct
|
|
|
|
|
)
|
2021-06-20 15:12:25 +02:00
|
|
|
|
) { conversationLocal(pos, a, statusIdAccess) }
|
2021-05-23 14:54:52 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-27 04:15:59 +02:00
|
|
|
|
// その他の実アカウント
|
2021-06-20 15:12:25 +02:00
|
|
|
|
SavedAccount.sort(otherAccountList)
|
|
|
|
|
for (a in otherAccountList) {
|
2021-05-27 04:15:59 +02:00
|
|
|
|
dialog.addAction(
|
|
|
|
|
AcctColor.getStringWithNickname(
|
|
|
|
|
activity,
|
|
|
|
|
R.string.open_in_account,
|
|
|
|
|
a.acct
|
|
|
|
|
)
|
|
|
|
|
) { conversationRemote(pos, a, url) }
|
2021-05-23 14:54:52 +02:00
|
|
|
|
}
|
|
|
|
|
|
2021-05-27 04:15:59 +02:00
|
|
|
|
dialog.show(activity, activity.getString(R.string.open_status_from))
|
|
|
|
|
}
|
2021-05-23 14:54:52 +02:00
|
|
|
|
|
2021-05-27 04:15:59 +02:00
|
|
|
|
// リモートかもしれない会話の流れを表示する
|
|
|
|
|
fun ActMain.conversationOtherInstance(
|
|
|
|
|
pos: Int,
|
2021-06-20 15:12:25 +02:00
|
|
|
|
status: TootStatus?,
|
2021-05-27 04:15:59 +02:00
|
|
|
|
) {
|
2021-06-21 05:03:09 +02:00
|
|
|
|
// URLが不明なトゥートというのはreblogの外側のアレ
|
|
|
|
|
val url = status?.url?.notEmpty() ?: return
|
2021-05-23 14:54:52 +02:00
|
|
|
|
|
2021-05-27 04:15:59 +02:00
|
|
|
|
when {
|
2021-05-23 14:54:52 +02:00
|
|
|
|
|
2021-05-27 04:15:59 +02:00
|
|
|
|
// 検索サービスではステータスTLをどのタンスから読んだのか分からない
|
|
|
|
|
status.readerApDomain == null ->
|
|
|
|
|
conversationOtherInstance(
|
|
|
|
|
pos,
|
|
|
|
|
url,
|
|
|
|
|
TootStatus.validStatusId(status.id)
|
|
|
|
|
?: TootStatus.findStatusIdFromUri(
|
|
|
|
|
status.uri,
|
|
|
|
|
status.url
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// TLアカウントのホストとトゥートのアカウントのホストが同じ
|
|
|
|
|
status.originalApDomain == status.readerApDomain ->
|
|
|
|
|
conversationOtherInstance(
|
|
|
|
|
pos,
|
|
|
|
|
url,
|
|
|
|
|
TootStatus.validStatusId(status.id)
|
|
|
|
|
?: TootStatus.findStatusIdFromUri(
|
|
|
|
|
status.uri,
|
|
|
|
|
status.url
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
else -> {
|
|
|
|
|
// トゥートを取得したタンスと投稿元タンスが異なる場合
|
|
|
|
|
// status.id はトゥートを取得したタンスでのIDである
|
|
|
|
|
// 投稿元タンスでのIDはuriやURLから調べる
|
|
|
|
|
// pleromaではIDがuuidなので失敗する(その時はURLを検索してIDを見つける)
|
|
|
|
|
conversationOtherInstance(
|
|
|
|
|
pos, url, TootStatus.findStatusIdFromUri(
|
|
|
|
|
status.uri,
|
|
|
|
|
status.url
|
|
|
|
|
), status.readerApDomain, TootStatus.validStatusId(status.id)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
fun ActMain.conversationMute(
|
2021-06-20 15:12:25 +02:00
|
|
|
|
accessInfo: SavedAccount,
|
2021-06-21 05:03:09 +02:00
|
|
|
|
status: TootStatus?,
|
2021-05-27 04:15:59 +02:00
|
|
|
|
) {
|
2021-06-21 05:03:09 +02:00
|
|
|
|
status ?: return
|
|
|
|
|
|
2021-05-27 04:15:59 +02:00
|
|
|
|
// toggle change
|
|
|
|
|
val bMute = !status.muted
|
|
|
|
|
|
|
|
|
|
launchMain {
|
2021-06-20 15:12:25 +02:00
|
|
|
|
var localStatus: TootStatus? = null
|
|
|
|
|
runApiTask(accessInfo) { client ->
|
2021-05-27 04:15:59 +02:00
|
|
|
|
client.request(
|
|
|
|
|
"/api/v1/statuses/${status.id}/${if (bMute) "mute" else "unmute"}",
|
|
|
|
|
"".toFormRequestBody().toPost()
|
|
|
|
|
)?.also { result ->
|
2021-06-20 15:12:25 +02:00
|
|
|
|
localStatus = TootParser(this, accessInfo).status(result.jsonObject)
|
2021-05-23 14:54:52 +02:00
|
|
|
|
}
|
2021-05-27 04:15:59 +02:00
|
|
|
|
}?.let { result ->
|
2021-06-20 15:12:25 +02:00
|
|
|
|
when (val ls = localStatus) {
|
2021-05-27 04:15:59 +02:00
|
|
|
|
null -> showToast(true, result.error)
|
2021-05-23 14:54:52 +02:00
|
|
|
|
|
2021-05-27 04:15:59 +02:00
|
|
|
|
else -> {
|
2021-06-20 15:12:25 +02:00
|
|
|
|
for (column in appState.columnList) {
|
|
|
|
|
if (accessInfo == column.accessInfo) {
|
|
|
|
|
column.findStatus(accessInfo.apDomain, ls.id) { _, status ->
|
2021-05-23 14:54:52 +02:00
|
|
|
|
status.muted = bMute
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-05-27 04:15:59 +02:00
|
|
|
|
showToast(
|
2021-05-23 14:54:52 +02:00
|
|
|
|
true,
|
|
|
|
|
if (bMute) R.string.mute_succeeded else R.string.unmute_succeeded
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-05-27 04:15:59 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
// tootsearch APIは投稿の返信元を示すreplyの情報がない。
|
|
|
|
|
// in_reply_to_idを参照するしかない
|
|
|
|
|
// ところがtootsearchでは投稿をどのタンスから読んだか分からないので、IDは全面的に信用できない。
|
|
|
|
|
// 疑似ではないアカウントを選んだ後に表示中の投稿を検索APIで調べて、そのリプライのIDを取得しなおす
|
|
|
|
|
fun ActMain.conversationFromTootsearch(
|
|
|
|
|
pos: Int,
|
2021-06-20 15:12:25 +02:00
|
|
|
|
statusArg: TootStatus?,
|
2021-05-27 04:15:59 +02:00
|
|
|
|
) {
|
|
|
|
|
statusArg ?: return
|
|
|
|
|
|
|
|
|
|
// step2: 選択したアカウントで投稿を検索して返信元の投稿のIDを調べる
|
|
|
|
|
fun step2(a: SavedAccount) = launchMain {
|
|
|
|
|
var tmp: TootStatus? = null
|
|
|
|
|
runApiTask(a) { client ->
|
|
|
|
|
val (result, status) = client.syncStatus(a, statusArg)
|
|
|
|
|
tmp = status
|
|
|
|
|
result
|
|
|
|
|
}?.let { result ->
|
|
|
|
|
val status = tmp
|
|
|
|
|
val replyId = status?.in_reply_to_id
|
|
|
|
|
when {
|
|
|
|
|
status == null -> showToast(true, result.error ?: "?")
|
|
|
|
|
replyId == null -> showToast(true, "showReplyTootsearch: in_reply_to_id is null")
|
|
|
|
|
else -> conversationLocal(pos, a, replyId)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// step 1: choose account
|
|
|
|
|
|
|
|
|
|
val host = statusArg.account.apDomain
|
2021-06-20 15:12:25 +02:00
|
|
|
|
val localAccountList = ArrayList<SavedAccount>()
|
|
|
|
|
val otherAccountList = ArrayList<SavedAccount>()
|
2021-05-27 04:15:59 +02:00
|
|
|
|
|
|
|
|
|
for (a in SavedAccount.loadAccountList(this)) {
|
|
|
|
|
|
|
|
|
|
// 検索APIはログイン必須なので疑似アカウントは使えない
|
|
|
|
|
if (a.isPseudo) continue
|
|
|
|
|
|
|
|
|
|
if (a.matchHost(host)) {
|
2021-06-20 15:12:25 +02:00
|
|
|
|
localAccountList.add(a)
|
2021-05-27 04:15:59 +02:00
|
|
|
|
} else {
|
2021-06-20 15:12:25 +02:00
|
|
|
|
otherAccountList.add(a)
|
2021-05-27 04:15:59 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val dialog = ActionsDialog()
|
|
|
|
|
|
2021-06-20 15:12:25 +02:00
|
|
|
|
SavedAccount.sort(localAccountList)
|
|
|
|
|
for (a in localAccountList) {
|
2021-05-27 04:15:59 +02:00
|
|
|
|
dialog.addAction(
|
|
|
|
|
AcctColor.getStringWithNickname(
|
|
|
|
|
this,
|
|
|
|
|
R.string.open_in_account,
|
|
|
|
|
a.acct
|
|
|
|
|
)
|
|
|
|
|
) { step2(a) }
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-20 15:12:25 +02:00
|
|
|
|
SavedAccount.sort(otherAccountList)
|
|
|
|
|
for (a in otherAccountList) {
|
2021-05-27 04:15:59 +02:00
|
|
|
|
dialog.addAction(
|
|
|
|
|
AcctColor.getStringWithNickname(
|
|
|
|
|
this,
|
|
|
|
|
R.string.open_in_account,
|
|
|
|
|
a.acct
|
|
|
|
|
)
|
|
|
|
|
) { step2(a) }
|
2021-05-23 14:54:52 +02:00
|
|
|
|
}
|
|
|
|
|
|
2021-05-27 04:15:59 +02:00
|
|
|
|
dialog.show(this, getString(R.string.open_status_from))
|
|
|
|
|
}
|