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

1279 lines
36 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package jp.juggler.subwaytooter.action
import android.text.SpannableStringBuilder
import jp.juggler.subwaytooter.*
import jp.juggler.subwaytooter.api.*
import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.dialog.AccountPicker
import jp.juggler.subwaytooter.dialog.ActionsDialog
import jp.juggler.subwaytooter.dialog.DlgConfirm
import jp.juggler.subwaytooter.table.AcctColor
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.EmptyCallback
import jp.juggler.subwaytooter.util.SavedAccountCallback
import jp.juggler.util.*
import okhttp3.Request
import org.json.JSONObject
import java.util.*
import java.util.regex.Pattern
object Action_Toot {
private val log = LogCategory("Action_Toot")
private val reDetailedStatusTime =
Pattern.compile("<a\\b[^>]*?\\bdetailed-status__datetime\\b[^>]*href=\"https://[^/]+/@[^/]+/(\\d+)\"")
// アカウントを選んでお気に入り
fun favouriteFromAnotherAccount(
activity : ActMain,
timeline_account : SavedAccount,
status : TootStatus?
) {
if(status == null) return
val who_host = timeline_account.host
AccountPicker.pick(
activity,
bAllowPseudo = false,
bAuto = false,
message = activity.getString(R.string.account_picker_favourite),
accountListArg = makeAccountListNonPseudo(activity, who_host)
) { action_account ->
favourite(
activity,
action_account,
status,
calcCrossAccountMode(timeline_account, action_account),
callback = activity.favourite_complete_callback
)
}
}
// お気に入りの非同期処理
fun favourite(
activity : ActMain,
access_info : SavedAccount,
arg_status : TootStatus,
nCrossAccountMode : Int,
callback : EmptyCallback?,
bSet : Boolean = true,
bConfirmed : Boolean = false
) {
if(App1.getAppState(activity).isBusyFav(access_info, arg_status)) {
showToast(activity, false, R.string.wait_previous_operation)
return
}
// 必要なら確認を出す
if(! bConfirmed && ! access_info.isMisskey) {
DlgConfirm.open(
activity,
activity.getString(
when(bSet) {
true -> R.string.confirm_favourite_from
else -> R.string.confirm_unfavourite_from
},
AcctColor.getNickname(access_info.acct)
),
object : DlgConfirm.Callback {
override fun onOK() {
favourite(
activity,
access_info,
arg_status,
nCrossAccountMode,
callback,
bSet = bSet,
bConfirmed = true
)
}
override var isConfirmEnabled : Boolean
get() = when(bSet) {
true -> access_info.confirm_favourite
else -> access_info.confirm_unfavourite
}
set(value) {
when(bSet) {
true -> access_info.confirm_favourite = value
else -> access_info.confirm_unfavourite = value
}
access_info.saveSetting()
activity.reloadAccountSetting(access_info)
}
})
return
}
//
App1.getAppState(activity).setBusyFav(access_info, arg_status)
//
TootTaskRunner(activity, TootTaskRunner.PROGRESS_NONE).run(access_info, object : TootTask {
var new_status : TootStatus? = null
override fun background(client : TootApiClient) : TootApiResult? {
var result : TootApiResult?
val target_status : TootStatus
if(nCrossAccountMode == CROSS_ACCOUNT_REMOTE_INSTANCE) {
result = client.syncStatus(access_info, arg_status)
if(result?.data == null) return result
target_status = result.data as? TootStatus
?:
return TootApiResult(activity.getString(R.string.status_id_conversion_failed))
if(target_status.favourited) {
return TootApiResult(activity.getString(R.string.already_favourited))
}
} else {
target_status = arg_status
}
if(access_info.isMisskey) {
val params = access_info.putMisskeyApiToken(JSONObject())
.put("noteId", target_status.id.toString())
result = client.request(
if(bSet) {
"/api/notes/favorites/create"
} else {
"/api/notes/favorites/delete"
}
, params.toPostRequestBuilder()
)
// 正常レスポンスは 204 no content
// 既にお気に入り済みならエラー文字列に'already favorited' が返る
if(result?.response?.code() == 204
|| result?.error?.contains("already favorited") == true
|| result?.error?.contains("already not favorited") == true
) {
// 成功した
target_status.favourited = bSet
new_status = target_status
}
} else {
result = client.request(
"/api/v1/statuses/${target_status.id}/${if(bSet) "favourite" else "unfavourite"}",
"".toRequestBody().toPost()
)
val jsonObject = result?.jsonObject
new_status = TootParser(activity, access_info).status(jsonObject)
}
return result
}
override fun handleResult(result : TootApiResult?) {
App1.getAppState(activity).resetBusyFav(access_info, arg_status)
val new_status = this.new_status
when {
result == null -> {
} // cancelled.
new_status != null -> {
val old_count = arg_status.favourites_count
val new_count = new_status.favourites_count
if(old_count != null && new_count != null) {
if(access_info.isMisskey) {
new_status.favourited = bSet
}
if(bSet && new_status.favourited && new_count <= old_count) {
// 星をつけたのにカウントが上がらないのは違和感あるので、表示をいじる
new_status.favourites_count = old_count + 1L
} else if(! bSet && ! new_status.favourited && new_count >= old_count) {
// 星を外したのにカウントが下がらないのは違和感あるので、表示をいじる
// 0未満にはならない
new_status.favourites_count =
if(old_count < 1L) 0L else old_count - 1L
}
}
for(column in App1.getAppState(activity).column_list) {
column.findStatus(access_info.host, new_status.id) { account, status ->
// 同タンス別アカウントでもカウントは変化する
status.favourites_count = new_status.favourites_count
// 同アカウントならfav状態を変化させる
if(access_info.acct == account.acct) {
status.favourited = new_status.favourited
}
true
}
}
if(callback != null) callback()
}
else -> showToast(activity, true, result.error)
}
// 結果に関わらず、更新中状態から復帰させる
activity.showColumnMatchAccount(access_info)
}
})
// ファボ表示を更新中にする
activity.showColumnMatchAccount(access_info)
}
fun boostFromAnotherAccount(
activity : ActMain,
timeline_account : SavedAccount,
status : TootStatus?
) {
status ?: return
val who_host = timeline_account.host
val status_owner = timeline_account.getFullAcct(status.account)
val isPrivateToot =
! timeline_account.isMisskey && status.visibility == TootVisibility.PrivateFollowers
if(isPrivateToot) {
val list = ArrayList<SavedAccount>()
for(a in SavedAccount.loadAccountList(activity)) {
if(a.acct == status_owner) list.add(a)
}
if(list.isEmpty()) {
showToast(activity, false, R.string.boost_private_toot_not_allowed)
return
}
AccountPicker.pick(
activity,
bAllowPseudo = false,
bAuto = false,
message = activity.getString(R.string.account_picker_boost),
accountListArg = list
) { action_account ->
boost(
activity,
action_account,
status,
status_owner,
calcCrossAccountMode(timeline_account, action_account),
activity.boost_complete_callback
)
}
} else {
AccountPicker.pick(
activity,
bAllowPseudo = false,
bAuto = false,
message = activity.getString(R.string.account_picker_boost),
accountListArg = makeAccountListNonPseudo(activity, who_host)
) { action_account ->
boost(
activity,
action_account,
status,
status_owner,
calcCrossAccountMode(timeline_account, action_account),
activity.boost_complete_callback
)
}
}
}
fun boost(
activity : ActMain,
access_info : SavedAccount,
arg_status : TootStatus,
status_owner_acct : String,
nCrossAccountMode : Int,
callback : EmptyCallback?,
bSet : Boolean = true,
bConfirmed : Boolean = false
) {
// アカウントからステータスにブースト操作を行っているなら、何もしない
if(App1.getAppState(activity).isBusyBoost(access_info, arg_status)) {
showToast(activity, false, R.string.wait_previous_operation)
return
}
// 非公開トゥートをブーストできるのは本人だけ
val isPrivateToot =
! access_info.isMisskey && arg_status.visibility == TootVisibility.PrivateFollowers
if(isPrivateToot && access_info.acct != status_owner_acct) {
showToast(activity, false, R.string.boost_private_toot_not_allowed)
return
}
// 必要なら確認を出す
if(! bConfirmed) {
DlgConfirm.open(
activity,
activity.getString(
when {
! bSet -> R.string.confirm_unboost_from
isPrivateToot -> R.string.confirm_boost_private_from
else -> R.string.confirm_boost_from
},
AcctColor.getNickname(access_info.acct)
),
object : DlgConfirm.Callback {
override fun onOK() {
boost(
activity,
access_info,
arg_status,
status_owner_acct,
nCrossAccountMode,
callback,
bSet = bSet,
bConfirmed = true
)
}
override var isConfirmEnabled : Boolean
get() = when(bSet) {
true -> access_info.confirm_boost
else -> access_info.confirm_unboost
}
set(value) {
when(bSet) {
true -> access_info.confirm_boost = value
else -> access_info.confirm_unboost = value
}
access_info.saveSetting()
activity.reloadAccountSetting(access_info)
}
})
return
}
App1.getAppState(activity).setBusyBoost(access_info, arg_status)
TootTaskRunner(activity, TootTaskRunner.PROGRESS_NONE).run(access_info, object : TootTask {
var new_status : TootStatus? = null
override fun background(client : TootApiClient) : TootApiResult? {
val parser = TootParser(activity, access_info)
var result : TootApiResult?
val target_status : TootStatus
if(nCrossAccountMode == CROSS_ACCOUNT_REMOTE_INSTANCE) {
result = client.syncStatus(access_info, arg_status)
if(result?.data == null) return result
target_status = result.data as? TootStatus
?:
return TootApiResult(activity.getString(R.string.status_id_conversion_failed))
if(target_status.reblogged) {
return TootApiResult(activity.getString(R.string.already_boosted))
}
} else {
// 既に自タンスのステータスがある
target_status = arg_status
}
if(access_info.isMisskey) {
if(! bSet) {
return TootApiResult("Misskey has no 'unrenote' API.")
} else {
val params = access_info.putMisskeyApiToken(JSONObject())
.put("renoteId", target_status.id.toString())
result = client.request("/api/notes/create", params.toPostRequestBuilder())
val jsonObject = result?.jsonObject
if(jsonObject != null) {
val new_status = parser.status(
jsonObject.optJSONObject("createdNote") ?: jsonObject
)
// renoteそのものではなくrenoteされた元noteが欲しい
this.new_status = new_status?.reblog ?: new_status
}
return result
}
} else {
result = client.request(
"/api/v1/statuses/${target_status.id}/${if(bSet) "reblog" else "unreblog"}",
"".toRequestBody().toPost()
)
// reblogはreblogを表すStatusを返す
// unreblogはreblogしたStatusを返す
val s = parser.status(result?.jsonObject)
this.new_status = s?.reblog ?: s
return result
}
}
override fun handleResult(result : TootApiResult?) {
App1.getAppState(activity).resetBusyBoost(access_info, arg_status)
val new_status = this.new_status
when {
result == null -> {
} // cancelled.
new_status != null -> {
// カウント数は遅延があるみたいなので、恣意的に表示を変更する
// ブーストカウント数を加工する
val old_count = arg_status.reblogs_count
val new_count = new_status.reblogs_count
if(old_count != null && new_count != null) {
if(bSet && new_status.reblogged && new_count <= old_count) {
// 星をつけたのにカウントが上がらないのは違和感あるので、表示をいじる
new_status.reblogs_count = old_count + 1
} else if(! bSet && ! new_status.reblogged && new_count >= old_count) {
// 星を外したのにカウントが下がらないのは違和感あるので、表示をいじる
// 0未満にはならない
new_status.reblogs_count = if(old_count < 1) 0 else old_count - 1
}
}
for(column in App1.getAppState(activity).column_list) {
column.findStatus(access_info.host, new_status.id) { account, status ->
// 同タンス別アカウントでもカウントは変化する
status.reblogs_count = new_status.reblogs_count
if(access_info.acct == account.acct) {
// 同アカウントならreblog状態を変化させる
status.reblogged = new_status.reblogged
}
true
}
}
if(callback != null) callback()
}
else -> showToast(activity, true, result.error)
}
// 結果に関わらず、更新中状態から復帰させる
activity.showColumnMatchAccount(access_info)
}
})
// ブースト表示を更新中にする
activity.showColumnMatchAccount(access_info)
}
fun delete(
activity : ActMain, access_info : SavedAccount, status_id : EntityId
) {
TootTaskRunner(activity).run(access_info, object : TootTask {
override fun background(client : TootApiClient) : TootApiResult? {
return if(access_info.isMisskey) {
val params = access_info.putMisskeyApiToken()
.put("noteId", status_id)
client.request("/api/notes/delete", params.toPostRequestBuilder())
// 204 no content
} else {
val request_builder = Request.Builder().delete()
client.request("/api/v1/statuses/$status_id", request_builder)
}
}
override fun handleResult(result : TootApiResult?) {
if(result == null) return // cancelled.
if(result.jsonObject != null) {
showToast(activity, false, R.string.delete_succeeded)
for(column in App1.getAppState(activity).column_list) {
column.onStatusRemoved(access_info.host, status_id)
}
} else {
showToast(activity, false, result.error)
}
}
})
}
/////////////////////////////////////////////////////////////////////////////////////
// open conversation
internal fun clearConversationUnread(
activity : ActMain,
access_info : SavedAccount,
conversationSummary : TootConversationSummary?
) {
conversationSummary ?: return
TootTaskRunner(activity, progress_style = TootTaskRunner.PROGRESS_NONE)
.run(access_info, object : TootTask {
override fun background(client : TootApiClient) : TootApiResult? {
return client.request(
"/api/v1/conversations/${conversationSummary.id}/read",
"".toRequestBody().toPost()
)
}
override fun handleResult(result : TootApiResult?) {
// 何もしない
}
})
}
// ローカルかリモートか判断する
fun conversation(
activity : ActMain,
pos : Int,
access_info : SavedAccount,
status : TootStatus
) {
if(access_info.isNA || ! access_info.host.equals(status.host_access, ignoreCase = true)) {
conversationOtherInstance(activity, pos, status)
} else {
conversationLocal(activity, pos, access_info, status.id)
}
}
// ローカルから見える会話の流れを表示する
fun conversationLocal(
activity : ActMain,
pos : Int,
access_info : SavedAccount,
status_id : EntityId
) {
activity.addColumn(pos, access_info, Column.TYPE_CONVERSATION, status_id)
}
// リモートかもしれない会話の流れを表示する
fun conversationOtherInstance(
activity : ActMain, pos : Int, status : TootStatus?
) {
if(status == null) return
val url = status.url
if(url == null || url.isEmpty()) {
// URLが不明なトゥートというのはreblogの外側のアレ
return
}
when {
// 検索サービスではステータスTLをどのタンスから読んだのか分からない
status.host_access == null ->
conversationOtherInstance(
activity
, pos
, url
, TootStatus.validStatusId(status.id)
?: TootStatus.findStatusIdFromUri(
status.uri,
status.url,
bAllowStringId = true
)
)
// TLアカウントのホストとトゥートのアカウントのホストが同じ
status.host_original == status.host_access ->
conversationOtherInstance(
activity
, pos
, url
, TootStatus.validStatusId(status.id)
?: TootStatus.findStatusIdFromUri(
status.uri,
status.url,
bAllowStringId = true
)
)
else -> {
// トゥートを取得したタンスと投稿元タンスが異なる場合
// status.id はトゥートを取得したタンスでのIDである
// 投稿元タンスでのIDはuriやURLから調べる
// pleromaではIDがuuidなので失敗する(その時はURLを検索してIDを見つける)
conversationOtherInstance(
activity
, pos
, url
, TootStatus.findStatusIdFromUri(
status.uri,
status.url,
bAllowStringId = true
)
, status.host_access
, TootStatus.validStatusId(status.id)
)
}
}
}
// アプリ外部からURLを渡された場合に呼ばれる
fun conversationOtherInstance(
activity : ActMain,
pos : Int,
url : String,
status_id_original : EntityId? = null,
host_access : String? = null,
status_id_access : EntityId? = null
) {
val dialog = ActionsDialog()
val host_original = url.toUri().authority ?: ""
// 選択肢:ブラウザで表示する
dialog.addAction(
activity.getString(
R.string.open_web_on_host,
host_original
)
) { App1.openCustomTab(activity, url) }
// トゥートの投稿元タンスにあるアカウント
val local_account_list = ArrayList<SavedAccount>()
// TLを読んだタンスにあるアカウント
val access_account_list = ArrayList<SavedAccount>()
// その他のタンスにあるアカウント
val other_account_list = ArrayList<SavedAccount>()
for(a in SavedAccount.loadAccountList(activity)) {
// 疑似アカウントは後でまとめて処理する
if(a.isPseudo) continue
if(status_id_original != null && a.host.equals(host_original, ignoreCase = true)) {
// アクセス情報ステータスID でアクセスできるなら
// 同タンスのアカウントならステータスIDの変換なしに表示できる
local_account_list.add(a)
} else if(status_id_access != null && a.host.equals(host_access, ignoreCase = true)) {
// 既に変換済みのステータスIDがあるなら、そのアカウントでもステータスIDの変換は必要ない
access_account_list.add(a)
} else {
// 別タンスでも実アカウントなら検索APIでステータスIDを変換できる
other_account_list.add(a)
}
}
// 同タンスのアカウントがないなら、疑似アカウントで開く選択肢
if(local_account_list.isEmpty()) {
val isMisskey = TootStatus.reStatusPageMisskey.matcher(url).find()
if(status_id_original != null) {
dialog.addAction(
activity.getString(R.string.open_in_pseudo_account, "?@$host_original")
) {
addPseudoAccount(activity, host_original, isMisskey = isMisskey) { sa ->
conversationLocal(activity, pos, sa, status_id_original)
}
}
} else {
dialog.addAction(
activity.getString(R.string.open_in_pseudo_account, "?@$host_original")
) {
addPseudoAccount(activity, host_original, isMisskey = isMisskey) { sa ->
conversationRemote(activity, pos, sa, url)
}
}
}
}
// ローカルアカウント
if(status_id_original != null) {
SavedAccount.sort(local_account_list)
for(a in local_account_list) {
dialog.addAction(
AcctColor.getStringWithNickname(
activity,
R.string.open_in_account,
a.acct
)
) { conversationLocal(activity, pos, a, status_id_original) }
}
}
// アクセスしたアカウント
if(status_id_access != null) {
SavedAccount.sort(access_account_list)
for(a in access_account_list) {
dialog.addAction(
AcctColor.getStringWithNickname(
activity,
R.string.open_in_account,
a.acct
)
) { conversationLocal(activity, pos, a, status_id_access) }
}
}
// その他の実アカウント
SavedAccount.sort(other_account_list)
for(a in other_account_list) {
dialog.addAction(
AcctColor.getStringWithNickname(
activity,
R.string.open_in_account,
a.acct
)
) { conversationRemote(activity, pos, a, url) }
}
dialog.show(activity, activity.getString(R.string.open_status_from))
}
private fun conversationRemote(
activity : ActMain, pos : Int, access_info : SavedAccount, remote_status_url : String
) {
TootTaskRunner(activity)
.progressPrefix(activity.getString(R.string.progress_synchronize_toot))
.run(access_info, object : TootTask {
var local_status_id : EntityId? = null
override fun background(client : TootApiClient) : TootApiResult? {
var result : TootApiResult?
if(access_info.isPseudo) {
// 疑似アカウントではURLからIDを取得するのにHTMLと正規表現を使う
result = client.getHttp(remote_status_url)
val string = result?.string
if(string != null) {
try {
val m = reDetailedStatusTime.matcher(string)
if(m.find()) {
local_status_id = EntityIdLong(m.group(1).toLong(10))
}
} catch(ex : Throwable) {
log.e(ex, "openStatusRemote: can't parse status id from HTML data.")
}
if(local_status_id == null) {
result = TootApiResult(
activity.getString(R.string.status_id_conversion_failed)
)
}
}
} else {
result = client.syncStatus(access_info, remote_status_url)
if(result?.data == null) return result
val status = result.data as? TootStatus
?: return TootApiResult(activity.getString(R.string.status_id_conversion_failed))
local_status_id = status.id
log.d("status id conversion %s => %s", remote_status_url, status.id)
}
return result
}
override fun handleResult(result : TootApiResult?) {
if(result == null) return // cancelled.
val local_status_id = this.local_status_id
if(local_status_id != null) {
conversationLocal(activity, pos, access_info, local_status_id)
} else {
showToast(activity, true, result.error)
}
}
})
}
////////////////////////////////////////
// profile pin
fun pin(
activity : ActMain, access_info : SavedAccount, status : TootStatus, bSet : Boolean
) {
TootTaskRunner(activity)
.progressPrefix(activity.getString(R.string.profile_pin_progress))
.run(access_info, object : TootTask {
var new_status : TootStatus? = null
override fun background(client : TootApiClient) : TootApiResult? {
val result = client.request(
"/api/v1/statuses/${status.id}/${if(bSet) "pin" else "unpin"}",
"".toRequestBody().toPost()
)
new_status = TootParser(activity, access_info).status(result?.jsonObject)
return result
}
override fun handleResult(result : TootApiResult?) {
val new_status = this.new_status
when {
result == null -> {
// cancelled.
}
new_status != null -> {
for(column in App1.getAppState(activity).column_list) {
if(access_info.acct == column.access_info.acct) {
column.findStatus(
access_info.host,
new_status.id
) { _, status ->
status.pinned = bSet
true
}
}
}
}
else -> showToast(activity, true, result.error)
}
// 結果に関わらず、更新中状態から復帰させる
activity.showColumnMatchAccount(access_info)
}
})
}
/////////////////////////////////////////////////////////////////////////////////
// reply
fun reply(
activity : ActMain,
access_info : SavedAccount,
status : TootStatus,
quotedRenote : Boolean = false
) {
ActPost.open(
activity,
ActMain.REQUEST_CODE_POST,
access_info.db_id,
reply_status = status,
quotedRenote = quotedRenote
)
}
private fun replyRemote(
activity : ActMain,
access_info : SavedAccount,
remote_status_url : String?,
quotedRenote : Boolean = false
) {
if(remote_status_url == null || remote_status_url.isEmpty()) return
TootTaskRunner(activity)
.progressPrefix(activity.getString(R.string.progress_synchronize_toot))
.run(access_info, object : TootTask {
var local_status : TootStatus? = null
override fun background(client : TootApiClient) : TootApiResult? {
val result = client.syncStatus(access_info, remote_status_url)
if(result?.data == null) return result
local_status = result.data as? TootStatus
?:
return TootApiResult(activity.getString(R.string.status_id_conversion_failed))
return result
}
override fun handleResult(result : TootApiResult?) {
result ?: return // cancelled.
val ls = local_status
if(ls != null) {
reply(activity, access_info, ls, quotedRenote = quotedRenote)
} else {
showToast(activity, true, result.error)
}
}
})
}
fun replyFromAnotherAccount(
activity : ActMain,
timeline_account : SavedAccount,
status : TootStatus?,
quotedRenote : Boolean = false
) {
status ?: return
val who_host = timeline_account.host
val accountCallback : SavedAccountCallback = { ai ->
if(ai.host.equals(status.host_access, ignoreCase = true)) {
// アクセス元ホストが同じならステータスIDを使って返信できる
reply(activity, ai, status, quotedRenote = quotedRenote)
} else {
// それ以外の場合、ステータスのURLを検索APIに投げることで返信できる
replyRemote(activity, ai, status.url, quotedRenote = quotedRenote)
}
}
if(quotedRenote) {
AccountPicker.pick(
activity,
bAllowPseudo = false,
bAllowMisskey = true,
bAllowMastodon = false,
bAuto = true,
message = activity.getString(R.string.account_picker_quoted_renote),
callback = accountCallback
)
} else {
AccountPicker.pick(
activity,
bAllowPseudo = false,
bAuto = false,
message = activity.getString(R.string.account_picker_reply),
accountListArg = makeAccountListNonPseudo(activity, who_host),
callback = accountCallback
)
}
}
// 投稿画面を開く。初期テキストを指定する
fun redraft(
activity : ActMain,
accessInfo : SavedAccount,
status : TootStatus
) {
activity.post_helper.closeAcctPopup()
if(accessInfo.isMisskey) {
ActPost.open(
activity,
ActMain.REQUEST_CODE_POST,
accessInfo.db_id,
redraft_status = status,
reply_status = status.reply
)
return
}
if(status.in_reply_to_id == null) {
ActPost.open(
activity,
ActMain.REQUEST_CODE_POST,
accessInfo.db_id,
redraft_status = status
)
return
}
TootTaskRunner(activity).run(accessInfo, object : TootTask {
var reply_status : TootStatus? = null
override fun background(client : TootApiClient) : TootApiResult? {
val result = client.request("/api/v1/statuses/${status.in_reply_to_id}")
reply_status = TootParser(activity, accessInfo).status(result?.jsonObject)
return result
}
override fun handleResult(result : TootApiResult?) {
if(result == null) return // cancelled.
val reply_status = this.reply_status
if(reply_status != null) {
ActPost.open(
activity,
ActMain.REQUEST_CODE_POST,
accessInfo.db_id,
redraft_status = status,
reply_status = reply_status
)
return
}
val error = result.error ?: "(no information)"
showToast(activity, true, activity.getString(R.string.cant_sync_toot) + " : $error")
}
})
}
////////////////////////////////////////
fun muteConversation(
activity : ActMain, access_info : SavedAccount, status : TootStatus
) {
// toggle change
val bMute = ! status.muted
TootTaskRunner(activity).run(access_info, object : TootTask {
var local_status : TootStatus? = null
override fun background(client : TootApiClient) : TootApiResult? {
val result = client.request(
"/api/v1/statuses/${status.id}/${if(bMute) "mute" else "unmute"}",
"".toRequestBody().toPost()
)
local_status = TootParser(activity, access_info).status(result?.jsonObject)
return result
}
override fun handleResult(result : TootApiResult?) {
result ?: return // cancelled.
val ls = local_status
if(ls != null) {
for(column in App1.getAppState(activity).column_list) {
if(access_info.acct == column.access_info.acct) {
column.findStatus(access_info.host, ls.id) { _, status ->
status.muted = bMute
true
}
}
}
showToast(
activity,
true,
if(bMute) R.string.mute_succeeded else R.string.unmute_succeeded
)
} else {
showToast(activity, true, result.error)
}
}
})
}
fun reaction(
activity : ActMain,
access_info : SavedAccount,
arg_status : TootStatus,
status_owner_acct : String,
nCrossAccountMode : Int,
callback : EmptyCallback?,
bSet : Boolean = true,
code : String? = null
) {
if(access_info.isPseudo || ! access_info.isMisskey) return
// 自分の投稿にはリアクション出来ない
if(access_info.acct == status_owner_acct) {
showToast(activity, false, R.string.it_is_you)
return
}
if(bSet && code == null) {
val ad = ActionsDialog()
for(mr in MisskeyReaction.values()) {
val newCode = mr.shortcode
val sb = SpannableStringBuilder()
.appendDrawableIcon(activity, mr.drawableId, " ")
.append(' ')
.append(mr.shortcode)
ad.addAction(sb) {
reaction(
activity,
access_info,
arg_status,
status_owner_acct,
nCrossAccountMode,
callback,
bSet,
newCode
)
}
}
ad.show(activity)
return
}
TootTaskRunner(activity, TootTaskRunner.PROGRESS_NONE).run(access_info, object : TootTask {
override fun background(client : TootApiClient) : TootApiResult? {
val target_status : TootStatus
if(nCrossAccountMode == CROSS_ACCOUNT_REMOTE_INSTANCE) {
val result = client.syncStatus(access_info, arg_status)
if(result?.data == null) return result
target_status = result.data as? TootStatus
?: return TootApiResult(
activity.getString(R.string.status_id_conversion_failed)
)
if(target_status.myReaction != null) {
return TootApiResult(activity.getString(R.string.already_reactioned))
}
} else {
// 既に自タンスのステータスがある
target_status = arg_status
}
return if(! bSet) {
client.request(
"/api/notes/reactions/delete",
access_info.putMisskeyApiToken()
.put("noteId", target_status.id.toString())
.toPostRequestBuilder()
)
// 成功すると204 no content
} else {
client.request(
"/api/notes/reactions/create",
access_info.putMisskeyApiToken()
.put("noteId", target_status.id.toString())
.put("reaction", code)
.toPostRequestBuilder()
)
// 成功すると204 no content
}
}
override fun handleResult(result : TootApiResult?) {
result ?: return
val error = result.error
if(error != null) {
showToast(activity, false, error)
return
}
if(callback != null) callback()
}
})
}
fun reactionFromAnotherAccount(
activity : ActMain,
timeline_account : SavedAccount,
status : TootStatus?,
code : String? = null
) {
status ?: return
val status_owner = timeline_account.getFullAcct(status.account)
AccountPicker.pick(
activity,
bAllowPseudo = false,
bAllowMisskey = true,
bAllowMastodon = false,
bAuto = false,
message = activity.getString(R.string.account_picker_reaction)
) { action_account ->
reaction(
activity,
action_account,
status,
status_owner,
calcCrossAccountMode(timeline_account, action_account),
activity.reaction_complete_callback,
code = code
)
}
}
fun deleteScheduledPost(
activity : ActMain,
access_info : SavedAccount,
item : TootScheduled,
bConfirmed : Boolean = false,
callback : () -> Unit
) {
if(! bConfirmed) {
DlgConfirm.openSimple(
activity,
activity.getString(R.string.scheduled_status_delete_confirm)
) {
deleteScheduledPost(
activity,
access_info,
item,
bConfirmed = true,
callback = callback
)
}
return
}
TootTaskRunner(activity).run(access_info, object : TootTask {
override fun background(client : TootApiClient) : TootApiResult? {
return client.request(
"/api/v1/scheduled_statuses/${item.id}",
Request.Builder().delete()
)
}
override fun handleResult(result : TootApiResult?) {
result ?: return
val error = result.error
if(error != null) {
showToast(activity, false, error)
return
}
callback()
}
})
}
fun editScheduledPost(
activity : ActMain,
access_info : SavedAccount,
item : TootScheduled
) {
TootTaskRunner(activity).run(access_info, object : TootTask {
var reply_status : TootStatus? = null
override fun background(client : TootApiClient) : TootApiResult? {
val reply_status_id = item.in_reply_to_id
?: return TootApiResult()
return client.request("/api/v1/statuses/$reply_status_id")?.also { result ->
reply_status = TootParser(activity, access_info).status(result.jsonObject)
}
}
override fun handleResult(result : TootApiResult?) {
result ?: return
val error = result.error
if(error != null) {
showToast(activity, false, error)
return
}
ActPost.open(
activity,
ActMain.REQUEST_CODE_POST,
access_info.db_id,
scheduledStatus = item,
reply_status = reply_status
)
}
})
}
}