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

642 lines
23 KiB
Kotlin
Raw Normal View History

package jp.juggler.subwaytooter.action
2019-02-15 02:51:22 +01:00
import androidx.appcompat.app.AlertDialog
2021-05-17 16:13:04 +02:00
import jp.juggler.subwaytooter.*
import jp.juggler.subwaytooter.api.*
import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.dialog.DlgConfirm
import jp.juggler.subwaytooter.dialog.pickAccount
import jp.juggler.subwaytooter.table.AcctColor
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.table.UserRelation
import jp.juggler.util.*
2021-06-21 05:03:09 +02:00
fun ActMain.clickFollow(
pos: Int,
accessInfo: SavedAccount,
who: TootAccount,
whoRef: TootAccountRef,
relation: UserRelation,
) {
when {
accessInfo.isPseudo ->
followFromAnotherAccount(pos, accessInfo, who)
accessInfo.isMisskey &&
relation.getRequested(who) &&
!relation.getFollowing(who) ->
followRequestDelete(
pos, accessInfo, whoRef,
callback = cancelFollowRequestCompleteCallback
)
else -> {
val bSet = !(relation.getRequested(who) || relation.getFollowing(who))
follow(
pos,
accessInfo,
whoRef,
bFollow = bSet,
callback = when (bSet) {
true -> followCompleteCallback
else -> unfollowCompleteCallback
}
)
}
}
}
fun ActMain.follow(
pos: Int,
accessInfo: SavedAccount,
whoRef: TootAccountRef,
bFollow: Boolean = true,
bConfirmMoved: Boolean = false,
bConfirmed: Boolean = false,
callback: () -> Unit = {},
) {
val activity = this@follow
val who = whoRef.get()
if (accessInfo.isMe(who)) {
showToast(false, R.string.it_is_you)
return
}
if (!bConfirmMoved && bFollow && who.moved != null) {
AlertDialog.Builder(activity)
.setMessage(
getString(
R.string.jump_moved_user,
accessInfo.getFullAcct(who),
accessInfo.getFullAcct(who.moved)
)
)
.setPositiveButton(R.string.ok) { _, _ ->
userProfileFromAnotherAccount(
pos,
accessInfo,
who.moved
)
}
.setNeutralButton(R.string.ignore_suggestion) { _, _ ->
follow(
pos,
accessInfo,
whoRef,
bFollow = bFollow,
bConfirmMoved = true, // CHANGED
bConfirmed = bConfirmed,
callback = callback
)
}
.setNegativeButton(R.string.cancel, null)
.show()
return
}
if (!bConfirmed) {
if (bFollow && who.locked) {
DlgConfirm.open(
activity,
activity.getString(
R.string.confirm_follow_request_who_from,
whoRef.decoded_display_name,
AcctColor.getNickname(accessInfo)
),
object : DlgConfirm.Callback {
override fun onOK() {
follow(
pos,
accessInfo,
whoRef,
bFollow = bFollow,
bConfirmMoved = bConfirmMoved,
bConfirmed = true, // CHANGED
callback = callback
)
}
override var isConfirmEnabled: Boolean
get() = accessInfo.confirm_follow_locked
set(value) {
accessInfo.confirm_follow_locked = value
accessInfo.saveSetting()
activity.reloadAccountSetting(accessInfo)
}
})
return
} else if (bFollow) {
DlgConfirm.open(
activity,
getString(
R.string.confirm_follow_who_from,
whoRef.decoded_display_name,
AcctColor.getNickname(accessInfo)
),
object : DlgConfirm.Callback {
override fun onOK() {
follow(
pos,
accessInfo,
whoRef,
bFollow = bFollow,
bConfirmMoved = bConfirmMoved,
bConfirmed = true, //CHANGED
callback = callback
)
}
override var isConfirmEnabled: Boolean
get() = accessInfo.confirm_follow
set(value) {
accessInfo.confirm_follow = value
accessInfo.saveSetting()
activity.reloadAccountSetting(accessInfo)
}
})
return
} else {
DlgConfirm.open(
activity,
getString(
R.string.confirm_unfollow_who_from,
whoRef.decoded_display_name,
AcctColor.getNickname(accessInfo)
),
object : DlgConfirm.Callback {
override fun onOK() {
follow(
pos,
accessInfo,
whoRef,
bFollow = bFollow,
bConfirmMoved = bConfirmMoved,
bConfirmed = true, // CHANGED
callback = callback
)
}
override var isConfirmEnabled: Boolean
get() = accessInfo.confirm_unfollow
set(value) {
accessInfo.confirm_unfollow = value
accessInfo.saveSetting()
activity.reloadAccountSetting(accessInfo)
}
})
return
}
}
launchMain {
var resultRelation: UserRelation? = null
runApiTask(accessInfo, progressStyle = ApiTask.PROGRESS_NONE) { client ->
val parser = TootParser(activity, accessInfo)
var userId = who.id
if (who.isRemote) {
// リモートユーザの確認
val skipAccountSync = if (accessInfo.isMisskey) {
// Misskey の /users/show はリモートユーザに関して404を返すので
// userIdからリモートユーザを照合することはできない。
// ただし検索APIがエラーになるかどうかは未確認
false
} else {
// https://github.com/tateisu/SubwayTooter/issues/124
// によると、閉じたタンスのユーザを同期しようとすると検索APIがエラーを返す
// この問題を回避するため、手持ちのuserIdで照合したユーザのacctが目的のユーザと同じなら
// 検索APIを呼び出さないようにする
val result = client.request("/api/v1/accounts/$userId")
?: return@runApiTask null
who.acct == parser.account(result.jsonObject)?.acct
}
if (!skipAccountSync) {
// 同タンスのIDではなかった場合、検索APIを使う
val (result, ar) = client.syncAccountByAcct(accessInfo, who.acct)
val user = ar?.get() ?: return@runApiTask result
userId = user.id
}
}
if (accessInfo.isMisskey) {
client.request(
when {
bFollow -> "/api/following/create"
else -> "/api/following/delete"
},
accessInfo.putMisskeyApiToken().apply {
put("userId", userId)
}
.toPostRequestBuilder()
)?.also { result ->
fun saveFollow(f: Boolean) {
val ur = UserRelation.load(accessInfo.db_id, userId)
ur.following = f
UserRelation.save1Misskey(
System.currentTimeMillis(),
accessInfo.db_id,
userId.toString(),
ur
)
resultRelation = ur
}
val error = result.error
when {
// success
error == null -> saveFollow(bFollow)
// already followed/unfollowed
error.contains("already following") -> saveFollow(bFollow)
error.contains("already not following") -> saveFollow(bFollow)
// else something error
}
}
} else {
client.request(
"/api/v1/accounts/$userId/${if (bFollow) "follow" else "unfollow"}",
"".toFormRequestBody().toPost()
)?.also { result ->
val newRelation = parseItem(::TootRelationShip, parser, result.jsonObject)
resultRelation = accessInfo.saveUserRelation(newRelation)
}
}
}?.let { result ->
val relation = resultRelation
when {
relation != null -> {
when {
// 鍵付きアカウントにフォローリクエストを申請した状態
bFollow && relation.getRequested(who) ->
showToast(false, R.string.follow_requested)
!bFollow && relation.getRequested(who) ->
showToast(false, R.string.follow_request_cant_remove_by_sender)
// ローカル操作成功、もしくはリモートフォロー成功
else -> callback()
}
showColumnMatchAccount(accessInfo)
}
bFollow && who.locked && (result.response?.code ?: -1) == 422 ->
showToast(false, R.string.cant_follow_locked_user)
else -> showToast(false, result.error)
}
}
}
}
// acct で指定したユーザをリモートフォローする
private fun ActMain.followRemote(
accessInfo: SavedAccount,
acct: Acct,
locked: Boolean,
bConfirmed: Boolean = false,
callback: () -> Unit = {},
) {
val activity = this@followRemote
if (accessInfo.isMe(acct)) {
showToast(false, R.string.it_is_you)
return
}
if (!bConfirmed) {
if (locked) {
DlgConfirm.open(
activity,
getString(
R.string.confirm_follow_request_who_from,
AcctColor.getNickname(acct),
AcctColor.getNickname(accessInfo)
),
object : DlgConfirm.Callback {
override fun onOK() {
followRemote(
accessInfo,
acct,
locked,
bConfirmed = true, //CHANGE
callback = callback
)
}
override var isConfirmEnabled: Boolean
get() = accessInfo.confirm_follow_locked
set(value) {
accessInfo.confirm_follow_locked = value
accessInfo.saveSetting()
reloadAccountSetting(accessInfo)
}
})
return
} else {
DlgConfirm.open(
activity,
getString(
R.string.confirm_follow_who_from,
AcctColor.getNickname(acct),
AcctColor.getNickname(accessInfo)
),
object : DlgConfirm.Callback {
override fun onOK() {
followRemote(
accessInfo,
acct,
locked,
bConfirmed = true, //CHANGE
callback = callback
)
}
override var isConfirmEnabled: Boolean
get() = accessInfo.confirm_follow
set(value) {
accessInfo.confirm_follow = value
accessInfo.saveSetting()
reloadAccountSetting(accessInfo)
}
})
return
}
}
launchMain {
var resultRelation: UserRelation? = null
runApiTask(accessInfo, progressStyle = ApiTask.PROGRESS_NONE) { client ->
val parser = TootParser(this, accessInfo)
val (r2, ar) = client.syncAccountByAcct(accessInfo, acct)
val user = ar?.get() ?: return@runApiTask r2
val userId = user.id
if (accessInfo.isMisskey) {
client.request(
"/api/following/create",
accessInfo.putMisskeyApiToken().apply {
put("userId", userId)
}.toPostRequestBuilder()
).also { result ->
if (result?.error?.contains("already following") == true ||
result?.error?.contains("already not following") == true
) {
// DBから読み直して値を変更する
resultRelation = UserRelation.load(accessInfo.db_id, userId)
.apply { following = true }
} else {
// parserに残ってるRelationをDBに保存する
parser.account(result?.jsonObject)?.let {
resultRelation = accessInfo.saveUserRelationMisskey(it.id, parser)
}
}
}
} else {
client.request(
"/api/v1/accounts/$userId/follow",
"".toFormRequestBody().toPost()
)?.also { result ->
parseItem(::TootRelationShip, parser, result.jsonObject)?.let {
resultRelation = accessInfo.saveUserRelation(it)
}
}
}
}?.let { result ->
when {
resultRelation != null -> {
callback()
showColumnMatchAccount(accessInfo)
}
locked && (result.response?.code ?: -1) == 422 ->
showToast(false, R.string.cant_follow_locked_user)
else ->
showToast(false, result.error)
}
}
}
}
fun ActMain.followFromAnotherAccount(
pos: Int,
accessInfo: SavedAccount,
account: TootAccount?,
bConfirmMoved: Boolean = false,
) {
account ?: return
if (!bConfirmMoved && account.moved != null) {
AlertDialog.Builder(this)
.setMessage(
getString(
R.string.jump_moved_user,
accessInfo.getFullAcct(account),
accessInfo.getFullAcct(account.moved)
)
)
.setPositiveButton(R.string.ok) { _, _ ->
userProfileFromAnotherAccount(pos, accessInfo, account.moved)
}
.setNeutralButton(R.string.ignore_suggestion) { _, _ ->
followFromAnotherAccount(
pos,
accessInfo,
account,
bConfirmMoved = true //CHANGED
)
}
.setNegativeButton(android.R.string.cancel, null)
.show()
return
}
val whoAcct = accessInfo.getFullAcct(account)
launchMain {
pickAccount(
bAuto = false,
message = getString(R.string.account_picker_follow),
accountListArg = accountListNonPseudo(account.apiHost)
)?.let {
followRemote(
it,
whoAcct,
account.locked,
callback = followCompleteCallback
)
}
}
}
fun ActMain.followRequestAuthorize(
accessInfo: SavedAccount,
whoRef: TootAccountRef,
bAllow: Boolean,
) {
val who = whoRef.get()
if (accessInfo.isMe(who)) {
showToast(false, R.string.it_is_you)
return
}
launchMain {
runApiTask(accessInfo) { client ->
val parser = TootParser(this, accessInfo)
if (accessInfo.isMisskey) {
client.request(
"/api/following/requests/${if (bAllow) "accept" else "reject"}",
accessInfo.putMisskeyApiToken().apply {
put("userId", who.id)
}
.toPostRequestBuilder()
).also { result ->
val user = parser.account(result?.jsonObject)
if (user != null) {
// parserに残ってるRelationをDBに保存する
accessInfo.saveUserRelationMisskey(user.id, parser)
}
// 読めなくてもエラー処理は行わない
}
} else {
client.request(
"/api/v1/follow_requests/${who.id}/${if (bAllow) "authorize" else "reject"}",
"".toFormRequestBody().toPost()
)?.also { result ->
// Mastodon 3.0.0 から更新されたリレーションを返す
// https//github.com/tootsuite/mastodon/pull/11800
val newRelation = parseItem(::TootRelationShip, parser, result.jsonObject)
accessInfo.saveUserRelation(newRelation)
// 読めなくてもエラー処理は行わない
}
}
}?.let { result ->
when (result.jsonObject) {
null -> showToast(false, result.error)
else -> {
for (column in appState.columnList) {
column.removeUser(accessInfo, ColumnType.FOLLOW_REQUESTS, who.id)
// 他のカラムでもフォロー状態の表示更新が必要
if (column.accessInfo == accessInfo &&
column.type != ColumnType.FOLLOW_REQUESTS
) {
column.fireRebindAdapterItems()
}
}
showToast(
false,
if (bAllow) R.string.follow_request_authorized else R.string.follow_request_rejected,
whoRef.decoded_display_name
)
}
}
}
}
}
fun ActMain.followRequestDelete(
pos: Int,
accessInfo: SavedAccount,
whoRef: TootAccountRef,
bConfirmed: Boolean = false,
callback: () -> Unit = {},
) {
if (!accessInfo.isMisskey) {
follow(
pos,
accessInfo,
whoRef,
bFollow = false,
bConfirmed = bConfirmed,
callback = callback
)
return
}
val who = whoRef.get()
if (accessInfo.isMe(who)) {
showToast(false, R.string.it_is_you)
return
}
if (!bConfirmed) {
DlgConfirm.openSimple(
this,
getString(
R.string.confirm_cancel_follow_request_who_from,
whoRef.decoded_display_name,
AcctColor.getNickname(accessInfo)
)
) {
followRequestDelete(
pos,
accessInfo,
whoRef,
bConfirmed = true, // CHANGED
callback = callback
)
}
return
}
launchMain {
var resultRelation: UserRelation? = null
runApiTask(accessInfo, progressStyle = ApiTask.PROGRESS_NONE) { client ->
if (!accessInfo.isMisskey) {
TootApiResult("Mastodon has no API to cancel follow request")
} else {
val parser = TootParser(this, accessInfo)
var userId: EntityId = who.id
// リモートユーザの同期
if (who.isRemote) {
val (result, ar) = client.syncAccountByAcct(accessInfo, who.acct)
val user = ar?.get() ?: return@runApiTask result
userId = user.id
}
client.request(
"/api/following/requests/cancel", accessInfo.putMisskeyApiToken().apply {
put("userId", userId)
}
.toPostRequestBuilder()
)?.also { result ->
parser.account(result.jsonObject)?.let {
// parserに残ってるRelationをDBに保存する
resultRelation = accessInfo.saveUserRelationMisskey(it.id, parser)
}
}
}
}?.let { result ->
when (resultRelation) {
null -> showToast(false, result.error)
else -> {
// ローカル操作成功、もしくはリモートフォロー成功
callback()
showColumnMatchAccount(accessInfo)
}
}
}
}
}