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

612 lines
21 KiB
Kotlin
Raw Normal View History

package jp.juggler.subwaytooter.action
2021-06-21 05:03:09 +02:00
import android.content.Intent
import androidx.appcompat.app.AlertDialog
2022-05-29 15:38:21 +02:00
import jp.juggler.subwaytooter.ActMain
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.actmain.addColumn
2021-06-28 09:09:00 +02:00
import jp.juggler.subwaytooter.actmain.reloadAccountSetting
import jp.juggler.subwaytooter.actmain.showColumnMatchAccount
import jp.juggler.subwaytooter.api.*
2022-05-29 15:38:21 +02:00
import jp.juggler.subwaytooter.api.entity.EntityId
import jp.juggler.subwaytooter.api.entity.TootScheduled
import jp.juggler.subwaytooter.api.entity.TootStatus
import jp.juggler.subwaytooter.column.*
2022-05-29 15:38:21 +02:00
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
import jp.juggler.subwaytooter.dialog.actionsDialog
import jp.juggler.subwaytooter.dialog.pickAccount
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.table.accountListNonPseudo
import jp.juggler.subwaytooter.table.daoAcctColor
import jp.juggler.subwaytooter.table.daoSavedAccount
2021-06-22 10:31:51 +02:00
import jp.juggler.subwaytooter.util.emptyCallback
import jp.juggler.util.coroutine.launchAndShowError
import jp.juggler.util.coroutine.launchMain
import jp.juggler.util.data.notEmpty
import jp.juggler.util.log.showToast
import jp.juggler.util.network.toFormRequestBody
import jp.juggler.util.network.toPost
import jp.juggler.util.network.toPostRequestBuilder
2022-05-29 15:38:21 +02:00
import kotlinx.coroutines.CancellationException
import okhttp3.Request
2021-06-22 10:31:51 +02:00
fun ActMain.clickStatusDelete(
2021-06-21 05:03:09 +02:00
accessInfo: SavedAccount,
status: TootStatus?,
) {
status ?: return
AlertDialog.Builder(this)
2021-06-22 10:31:51 +02:00
.setMessage(getString(R.string.confirm_delete_status))
2021-06-21 05:03:09 +02:00
.setNegativeButton(R.string.cancel, null)
2021-06-22 10:31:51 +02:00
.setPositiveButton(R.string.ok) { _, _ -> statusDelete(accessInfo, status.id) }
2021-06-21 05:03:09 +02:00
.show()
}
2021-06-22 10:31:51 +02:00
fun ActMain.clickBookmark(accessInfo: SavedAccount, status: TootStatus, willToast: Boolean) {
if (accessInfo.isPseudo) {
bookmarkFromAnotherAccount(accessInfo, status)
} else {
// トグル動作
val bSet = !status.bookmarked
bookmark(
accessInfo,
status,
CrossAccountMode.SameAccount,
bSet = bSet,
callback = when {
!willToast -> emptyCallback
// 簡略表示なら結果をトースト表示
bSet -> bookmarkCompleteCallback
else -> unbookmarkCompleteCallback
},
)
}
2021-06-21 05:03:09 +02:00
}
2021-06-22 10:31:51 +02:00
fun ActMain.clickFavourite(accessInfo: SavedAccount, status: TootStatus, willToast: Boolean) {
if (accessInfo.isPseudo) {
favouriteFromAnotherAccount(accessInfo, status)
} else {
// トグル動作
val bSet = !status.favourited
favourite(
accessInfo,
status,
CrossAccountMode.SameAccount,
bSet = bSet,
callback = when {
!willToast -> emptyCallback
// 簡略表示なら結果をトースト表示
bSet -> favouriteCompleteCallback
else -> unfavouriteCompleteCallback
},
)
}
}
fun ActMain.clickScheduledToot(accessInfo: SavedAccount, item: TootScheduled, column: Column) {
launchAndShowError {
actionsDialog {
action(getString(R.string.edit)) {
scheduledPostEdit(accessInfo, item)
}
action(getString(R.string.delete)) {
launchAndShowError {
scheduledPostDelete(accessInfo, item)
column.onScheduleDeleted(item)
showToast(false, R.string.scheduled_post_deleted)
}
2021-06-22 10:31:51 +02:00
}
}
}
2021-06-21 05:03:09 +02:00
}
fun ActMain.launchActText(intent: Intent) = arActText.launch(intent)
///////////////////////////////////////////////////////////////
// お気に入りの非同期処理
fun ActMain.favourite(
accessInfo: SavedAccount,
statusArg: TootStatus,
crossAccountMode: CrossAccountMode,
callback: () -> Unit,
bSet: Boolean = true,
) {
2022-05-29 15:38:21 +02:00
launchAndShowError {
if (appState.isBusyFav(accessInfo, statusArg)) {
showToast(false, R.string.wait_previous_operation)
return@launchAndShowError
}
2022-05-29 15:38:21 +02:00
// 必要なら確認を出す
if (accessInfo.isMastodon) {
confirm(
getString(
when (bSet) {
true -> R.string.confirm_favourite_from
else -> R.string.confirm_unfavourite_from
},
daoAcctColor.getNickname(accessInfo)
2022-05-29 15:38:21 +02:00
),
when (bSet) {
2022-05-29 15:38:21 +02:00
true -> accessInfo.confirm_favourite
else -> accessInfo.confirm_unfavourite
}
2022-05-29 15:38:21 +02:00
) { newConfirmEnabled ->
when (bSet) {
true -> accessInfo.confirm_favourite = newConfirmEnabled
else -> accessInfo.confirm_unfavourite = newConfirmEnabled
}
daoSavedAccount.saveSetting(accessInfo)
2022-05-29 15:38:21 +02:00
reloadAccountSetting(accessInfo)
}
}
2022-05-29 15:38:21 +02:00
//
appState.setBusyFav(accessInfo, statusArg)
2022-05-29 15:38:21 +02:00
// ファボ表示を更新中にする
showColumnMatchAccount(accessInfo)
var resultStatus: TootStatus? = null
val result = runApiTask(
accessInfo,
progressStyle = ApiTask.PROGRESS_NONE
) { client ->
val targetStatus = if (crossAccountMode.isRemote) {
val (result, status) = client.syncStatus(accessInfo, statusArg)
status ?: return@runApiTask result
if (status.favourited) {
return@runApiTask TootApiResult(getString(R.string.already_favourited))
}
status
} else {
statusArg
}
if (accessInfo.isMisskey) {
client.request(
if (bSet) {
"/api/notes/favorites/create"
} else {
"/api/notes/favorites/delete"
},
accessInfo.putMisskeyApiToken().apply {
put("noteId", targetStatus.id.toString())
}
.toPostRequestBuilder()
)?.also { result ->
// 正常レスポンスは 204 no content
// 既にお気に入り済みならエラー文字列に "already favorited" が返る
if (result.response?.code == 204 ||
result.error?.contains("already favorited") == true ||
result.error?.contains("already not favorited") == true
) {
// 成功した
resultStatus = targetStatus.apply { favourited = bSet }
}
}
} else {
client.request(
"/api/v1/statuses/${targetStatus.id}/${if (bSet) "favourite" else "unfavourite"}",
"".toFormRequestBody().toPost()
)?.also { result ->
resultStatus = TootParser(this, accessInfo).status(result.jsonObject)
}
}
}
appState.resetBusyFav(accessInfo, statusArg)
if (result != null) {
when (val newStatus = resultStatus) {
null -> showToast(true, result.error)
else -> {
val oldCount = statusArg.favourites_count
val newCount = newStatus.favourites_count
if (oldCount != null && newCount != null) {
if (accessInfo.isMisskey) {
newStatus.favourited = bSet
}
if (bSet && newStatus.favourited && newCount <= oldCount) {
// 星をつけたのにカウントが上がらないのは違和感あるので、表示をいじる
newStatus.favourites_count = oldCount + 1L
} else if (!bSet && !newStatus.favourited && newCount >= oldCount) {
// 星を外したのにカウントが下がらないのは違和感あるので、表示をいじる
// 0未満にはならない
newStatus.favourites_count =
if (oldCount < 1L) 0L else oldCount - 1L
}
}
for (column in appState.columnList) {
column.findStatus(
accessInfo.apDomain,
newStatus.id
) { account, status ->
// 同タンス別アカウントでもカウントは変化する
status.favourites_count = newStatus.favourites_count
// 同アカウントならfav状態を変化させる
if (accessInfo == account) {
status.favourited = newStatus.favourited
}
true
}
}
callback()
}
}
}
// 結果に関わらず、更新中状態から復帰させる
showColumnMatchAccount(accessInfo)
}
}
// アカウントを選んでお気に入り
fun ActMain.favouriteFromAnotherAccount(
timelineAccount: SavedAccount,
status: TootStatus?,
) {
status ?: return
launchMain {
pickAccount(
bAllowPseudo = false,
bAuto = false,
message = getString(R.string.account_picker_favourite),
accountListArg = accountListNonPseudo(timelineAccount.apDomain)
)?.let { action_account ->
favourite(
action_account,
status,
calcCrossAccountMode(timelineAccount, action_account),
callback = favouriteCompleteCallback
)
}
}
}
////////////////////////////////////////////////////////////////
// お気に入りの非同期処理
fun ActMain.bookmark(
accessInfo: SavedAccount,
statusArg: TootStatus,
crossAccountMode: CrossAccountMode,
callback: () -> Unit,
bSet: Boolean = true,
) {
if (appState.isBusyFav(accessInfo, statusArg)) {
showToast(false, R.string.wait_previous_operation)
return
}
if (accessInfo.isMisskey) {
showToast(false, R.string.misskey_account_not_supported)
return
}
2022-05-29 15:38:21 +02:00
launchAndShowError {
// 必要なら確認を出す
// ブックマークは解除する時だけ確認する
if (!bSet) {
confirm(
getString(
R.string.confirm_unbookmark_from,
daoAcctColor.getNickname(accessInfo)
2022-05-29 15:38:21 +02:00
),
accessInfo.confirm_unbookmark
) { newConfirmEnabled ->
accessInfo.confirm_unbookmark = newConfirmEnabled
daoSavedAccount.saveSetting(accessInfo)
2022-05-29 15:38:21 +02:00
reloadAccountSetting(accessInfo)
}
}
2022-05-29 15:38:21 +02:00
//
appState.setBusyBookmark(accessInfo, statusArg)
2022-05-29 15:38:21 +02:00
// ファボ表示を更新中にする
showColumnMatchAccount(accessInfo)
var resultStatus: TootStatus? = null
val result = runApiTask(accessInfo, progressStyle = ApiTask.PROGRESS_NONE) { client ->
val targetStatus = if (crossAccountMode.isRemote) {
val (result, status) = client.syncStatus(accessInfo, statusArg)
status ?: return@runApiTask result
if (status.bookmarked) {
return@runApiTask TootApiResult(getString(R.string.already_bookmarked))
}
status
} else {
statusArg
}
client.request(
"/api/v1/statuses/${targetStatus.id}/${if (bSet) "bookmark" else "unbookmark"}",
"".toFormRequestBody().toPost()
)?.also { result ->
resultStatus = TootParser(this, accessInfo).status(result.jsonObject)
}
}
appState.resetBusyBookmark(accessInfo, statusArg)
if (result != null) {
when (val newStatus = resultStatus) {
null -> showToast(true, result.error)
else -> {
for (column in appState.columnList) {
column.findStatus(
accessInfo.apDomain,
newStatus.id
) { account, status ->
// 同アカウントならブックマーク状態を伝播する
if (accessInfo == account) {
status.bookmarked = newStatus.bookmarked
}
true
}
}
callback()
}
}
}
// 結果に関わらず、更新中状態から復帰させる
showColumnMatchAccount(accessInfo)
}
}
// アカウントを選んでブックマーク
fun ActMain.bookmarkFromAnotherAccount(
timelineAccount: SavedAccount,
status: TootStatus?,
) {
status ?: return
launchMain {
pickAccount(
bAllowPseudo = false,
bAuto = false,
message = getString(R.string.account_picker_bookmark),
accountListArg = accountListNonPseudo(timelineAccount.apDomain)
)?.let { action_account ->
bookmark(
action_account,
status,
calcCrossAccountMode(timelineAccount, action_account),
callback = bookmarkCompleteCallback
)
}
}
}
///////////////////////////////////////////////////////////////
fun ActMain.statusDelete(
accessInfo: SavedAccount,
statusId: EntityId,
) {
launchMain {
runApiTask(accessInfo) { client ->
if (accessInfo.isMisskey) {
client.request(
"/api/notes/delete",
accessInfo.putMisskeyApiToken().apply {
put("noteId", statusId)
}
.toPostRequestBuilder()
)
// 204 no content
} else {
client.request(
"/api/v1/statuses/$statusId",
Request.Builder().delete()
)
}
}?.let { result ->
if (result.jsonObject != null) {
showToast(false, R.string.delete_succeeded)
for (column in appState.columnList) {
column.onStatusRemoved(accessInfo.apDomain, statusId)
}
} else {
showToast(false, result.error)
}
}
}
}
////////////////////////////////////////
// profile pin
fun ActMain.statusPin(
accessInfo: SavedAccount,
2021-06-21 05:03:09 +02:00
status: TootStatus?,
bSet: Boolean,
) {
2021-06-21 05:03:09 +02:00
status ?: return
launchMain {
var resultStatus: TootStatus? = null
runApiTask(
accessInfo,
progressPrefix = getString(R.string.profile_pin_progress)
) { client ->
client.request(
"/api/v1/statuses/${status.id}/${if (bSet) "pin" else "unpin"}",
"".toFormRequestBody().toPost()
)?.also { result ->
resultStatus = TootParser(this, accessInfo).status(result.jsonObject)
}
}?.let { result ->
when (val newStatus = resultStatus) {
null -> showToast(true, result.error)
else -> {
for (column in appState.columnList) {
if (accessInfo == column.accessInfo) {
column.findStatus(
accessInfo.apDomain,
newStatus.id
) { _, status ->
status.pinned = bSet
true
}
}
}
showColumnMatchAccount(accessInfo)
}
}
}
}
}
// 投稿画面を開く。初期テキストを指定する
fun ActMain.statusRedraft(
accessInfo: SavedAccount,
2021-06-21 05:03:09 +02:00
status: TootStatus?,
) {
2021-06-21 05:03:09 +02:00
status ?: return
completionHelper.closeAcctPopup()
when {
accessInfo.isMisskey ->
openActPostImpl(
accessInfo.db_id,
redraftStatus = status,
replyStatus = status.reply
)
status.in_reply_to_id == null ->
openActPostImpl(
accessInfo.db_id,
redraftStatus = status
)
else -> launchMain {
var resultStatus: TootStatus? = null
runApiTask(accessInfo) { client ->
client.request("/api/v1/statuses/${status.in_reply_to_id}")
?.also { resultStatus = TootParser(this, accessInfo).status(it.jsonObject) }
}?.let { result ->
when (val replyStatus = resultStatus) {
null -> showToast(
true,
"${getString(R.string.cant_sync_toot)} : ${result.error ?: "(no information)"}"
)
else -> openActPostImpl(
accessInfo.db_id,
redraftStatus = status,
replyStatus = replyStatus
)
}
}
}
}
}
// 投稿画面を開く。初期テキストを指定する
fun ActMain.statusEdit(
accessInfo: SavedAccount,
status: TootStatus?,
) {
status ?: return
completionHelper.closeAcctPopup()
when {
accessInfo.isMisskey ->
openActPostImpl(
accessInfo.db_id,
editStatus = status,
replyStatus = status.reply
)
status.in_reply_to_id == null ->
openActPostImpl(
accessInfo.db_id,
editStatus = status
)
else -> launchMain {
var resultStatus: TootStatus? = null
runApiTask(accessInfo) { client ->
client.request("/api/v1/statuses/${status.in_reply_to_id}")
?.also { resultStatus = TootParser(this, accessInfo).status(it.jsonObject) }
}?.let { result ->
when (val replyStatus = resultStatus) {
null -> showToast(
true,
"${getString(R.string.cant_sync_toot)} : ${result.error ?: "(no information)"}"
)
else -> openActPostImpl(
accessInfo.db_id,
editStatus = status,
replyStatus = replyStatus
)
}
}
}
}
}
2022-05-29 15:38:21 +02:00
suspend fun ActMain.scheduledPostDelete(
accessInfo: SavedAccount,
item: TootScheduled,
bConfirmed: Boolean = false,
) {
if (!bConfirmed) {
2022-05-29 15:38:21 +02:00
confirm(R.string.scheduled_status_delete_confirm)
}
2022-05-29 15:38:21 +02:00
val result = runApiTask(accessInfo) { client ->
client.request(
"/api/v1/scheduled_statuses/${item.id}",
Request.Builder().delete()
)
} ?: throw CancellationException("scheduledPostDelete cancelled.")
result.error?.notEmpty()?.let { error(it) }
}
fun ActMain.scheduledPostEdit(
accessInfo: SavedAccount,
item: TootScheduled,
) {
launchMain {
var resultStatus: TootStatus? = null
runApiTask(accessInfo) { client ->
val replyStatusId = item.inReplyToId ?: return@runApiTask TootApiResult()
client.request("/api/v1/statuses/$replyStatusId")?.also { result ->
resultStatus = TootParser(this, accessInfo).status(result.jsonObject)
}
}?.let { result ->
when (val error = result.error) {
null -> openActPostImpl(
accessInfo.db_id,
scheduledStatus = item,
replyStatus = resultStatus
)
else -> showToast(true, error)
}
}
}
}
// アカウントを選んでタイムラインカラムを追加
fun ActMain.openStatusHistory(
pos: Int,
accessInfo: SavedAccount,
status: TootStatus,
) {
addColumn(pos, accessInfo, ColumnType.STATUS_HISTORY, status.id, status.json)
}