(Mastodon 3.5.0)投稿を編集できるようにする

This commit is contained in:
tateisu 2022-03-15 20:39:37 +09:00
parent 510dc828cf
commit 8bba82b930
30 changed files with 567 additions and 305 deletions

View File

@ -403,6 +403,7 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener,
cbNotificationReaction,
cbNotificationVote,
cbNotificationPost,
cbNotificationUpdate,
cbLocked,
cbConfirmFollow,
cbConfirmFollowLockedUser,
@ -448,6 +449,7 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener,
cbNotificationReaction.isChecked = a.notification_reaction
cbNotificationVote.isChecked = a.notification_vote
cbNotificationPost.isChecked = a.notification_post
cbNotificationUpdate.isChecked = a.notification_update
cbConfirmFollow.isChecked = a.confirm_follow
cbConfirmFollowLockedUser.isChecked = a.confirm_follow_locked
@ -518,6 +520,7 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener,
cbNotificationReaction,
cbNotificationVote,
cbNotificationPost,
cbNotificationUpdate,
cbConfirmFollow,
cbConfirmFollowLockedUser,
cbConfirmUnfollow,
@ -578,6 +581,7 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener,
account.notification_reaction = cbNotificationReaction.isChecked
account.notification_vote = cbNotificationVote.isChecked
account.notification_post = cbNotificationPost.isChecked
account.notification_update = cbNotificationUpdate.isChecked
account.confirm_follow = cbConfirmFollow.isChecked
account.confirm_follow_locked = cbConfirmFollowLockedUser.isChecked

View File

@ -54,6 +54,7 @@ class ActPost : AppCompatActivity(),
const val KEY_ACCOUNT_DB_ID = "account_db_id"
const val KEY_REPLY_STATUS = "reply_status"
const val KEY_REDRAFT_STATUS = "redraft_status"
const val KEY_EDIT_STATUS = "edit_status"
const val KEY_INITIAL_TEXT = "initial_text"
const val KEY_SHARED_INTENT = "sent_intent"
const val KEY_QUOTE = "quote"
@ -69,6 +70,8 @@ class ActPost : AppCompatActivity(),
multiWindowMode: Boolean,
// 再編集する投稿。アカウントと同一のタンスであること
redraftStatus: TootStatus? = null,
// 編集する投稿。アカウントと同一のタンスであること
editStatus:TootStatus? = null,
// 返信対象の投稿。同一タンス上に同期済みであること
replyStatus: TootStatus? = null,
//初期テキスト
@ -84,6 +87,7 @@ class ActPost : AppCompatActivity(),
putExtra(KEY_ACCOUNT_DB_ID, accountDbId)
initialText?.let { putExtra(KEY_INITIAL_TEXT, it) }
redraftStatus?.let { putExtra(KEY_REDRAFT_STATUS, it.json.toString()) }
editStatus?.let { putExtra(KEY_EDIT_STATUS, it.json.toString()) }
replyStatus?.let {
putExtra(KEY_REPLY_STATUS, it.json.toString())
putExtra(KEY_QUOTE, quote)

View File

@ -66,6 +66,7 @@ internal suspend fun AppCompatActivity.addPseudoAccount(
account.notification_reaction = false
account.notification_vote = false
account.notification_post = false
account.notification_update = false
account.saveSetting()
return account
} catch (ex: Throwable) {

View File

@ -58,6 +58,9 @@ fun ActMain.openActPostImpl(
// 再編集する投稿。アカウントと同一のタンスであること
redraftStatus: TootStatus? = null,
// 編集する投稿。アカウントと同一のタンスであること
editStatus: TootStatus? = null,
// 返信対象の投稿。同一タンス上に同期済みであること
replyStatus: TootStatus? = null,
@ -81,6 +84,7 @@ fun ActMain.openActPostImpl(
context = this,
accountDbId = accountDbId,
redraftStatus = redraftStatus,
editStatus = editStatus,
replyStatus = replyStatus,
initialText = initialText,
sharedIntent = sharedIntent,

View File

@ -537,6 +537,51 @@ fun ActMain.statusRedraft(
}
}
// 投稿画面を開く。初期テキストを指定する
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
)
}
}
}
}
}
fun ActMain.scheduledPostDelete(
accessInfo: SavedAccount,
item: TootScheduled,

View File

@ -3,24 +3,34 @@ package jp.juggler.subwaytooter.actmain
import android.content.Intent
import jp.juggler.subwaytooter.ActMain
import jp.juggler.subwaytooter.ActPost
import jp.juggler.subwaytooter.pref.PrefI
import jp.juggler.subwaytooter.api.entity.Acct
import jp.juggler.subwaytooter.api.entity.EntityId
import jp.juggler.subwaytooter.column.ColumnType
import jp.juggler.subwaytooter.column.onStatusRemoved
import jp.juggler.subwaytooter.column.startLoading
import jp.juggler.subwaytooter.column.startRefreshForPost
import jp.juggler.subwaytooter.column.*
import jp.juggler.subwaytooter.pref.PrefI
import jp.juggler.util.decodeJsonObject
import jp.juggler.util.isLiveActivity
// マルチウィンドウモードでは投稿画面から直接呼ばれる
// 通常モードでは activityResultHandler 経由で呼ばれる
fun ActMain.onCompleteActPost(data: Intent) {
if (!isLiveActivity) return
postedAcct = data.getStringExtra(ActPost.EXTRA_POSTED_ACCT)?.let { Acct.parse(it) }
this.postedAcct = data.getStringExtra(ActPost.EXTRA_POSTED_ACCT)?.let { Acct.parse(it) }
if (data.extras?.containsKey(ActPost.EXTRA_POSTED_STATUS_ID) == true) {
postedStatusId = EntityId.from(data, ActPost.EXTRA_POSTED_STATUS_ID)
postedReplyId = EntityId.from(data, ActPost.EXTRA_POSTED_REPLY_ID)
postedRedraftId = EntityId.from(data, ActPost.EXTRA_POSTED_REDRAFT_ID)
val postedStatusId = postedStatusId
val statusJson = data.getStringExtra(ActPost.KEY_EDIT_STATUS)
?.decodeJsonObject()
if (statusJson != null && postedStatusId != null) {
appState.columnList
.filter { it.accessInfo.acct == postedAcct }
.forEach {
it.replaceStatus(postedStatusId, statusJson)
}
}
} else {
postedStatusId = null
}

View File

@ -6,12 +6,12 @@ import android.view.inputmethod.EditorInfo
import android.widget.TextView
import androidx.core.view.GravityCompat
import jp.juggler.subwaytooter.*
import jp.juggler.subwaytooter.actpost.CompletionHelper
import jp.juggler.subwaytooter.api.entity.TootStatus
import jp.juggler.subwaytooter.api.entity.TootVisibility
import jp.juggler.subwaytooter.dialog.pickAccount
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.actpost.CompletionHelper
import jp.juggler.subwaytooter.pref.PrefB
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.PostCompleteCallback
import jp.juggler.subwaytooter.util.PostImpl
import jp.juggler.util.hideKeyboard
@ -124,6 +124,7 @@ fun ActMain.performQuickPost(account: SavedAccount?) {
scheduledAt = 0L,
scheduledId = null,
redraftStatusId = null,
editStatusId = null,
emojiMapCustom = App1.custom_emoji_lister.getMap(account),
useQuoteToot = false,
callback = object : PostCompleteCallback {

View File

@ -46,26 +46,24 @@ fun ActPost.selectAccount(a: SavedAccount?) {
}
fun ActPost.canSwitchAccount(): Boolean {
val errStringId = when {
// 予約投稿の再編集はアカウント切り替えできない
scheduledStatus != null ->
R.string.cant_change_account_when_editing_scheduled_status
// 削除して再投稿はアカウント切り替えできない
states.redraftStatusId != null ->
R.string.cant_change_account_when_redraft
// 投稿の編集中はアカウント切り替えできない
states.editStatusId != null ->
R.string.cant_change_account_when_edit
// 添付ファイルがあったらはアカウント切り替えできない
attachmentList.isNotEmpty() ->
R.string.cant_change_account_when_attachment_specified
else -> null
} ?: return true
if (scheduledStatus != null) {
// 予約投稿の再編集ではアカウントを切り替えられない
showToast(false, R.string.cant_change_account_when_editing_scheduled_status)
return false
}
if (attachmentList.isNotEmpty()) {
// 添付ファイルがあったら確認の上添付ファイルを捨てないと切り替えられない
showToast(false, R.string.cant_change_account_when_attachment_specified)
return false
}
if (states.redraftStatusId != null) {
// 添付ファイルがあったら確認の上添付ファイルを捨てないと切り替えられない
showToast(false, R.string.cant_change_account_when_redraft)
return false
}
return true
showToast(true, errStringId)
return false
}
fun ActPost.performAccountChooser() {

View File

@ -422,3 +422,104 @@ fun ActPost.initializeFromRedraftStatus(account: SavedAccount, jsonText: String)
log.trace(ex)
}
}
fun ActPost.initializeFromEditStatus(account: SavedAccount, jsonText: String) {
try {
val baseStatus =
TootParser(this, account).status(jsonText.decodeJsonObject())
?: error("initializeFromEditStatus: parse failed.")
states.editStatusId = baseStatus.id
states.visibility = baseStatus.visibility
val srcAttachments = baseStatus.media_attachments
if (srcAttachments?.isNotEmpty() == true) {
saveAttachmentList()
this.attachmentList.clear()
try {
for (src in srcAttachments) {
if (src is TootAttachment) {
src.redraft = true
val pa = PostAttachment(src)
pa.status = PostAttachment.Status.Ok
this.attachmentList.add(pa)
}
}
} catch (ex: Throwable) {
log.trace(ex)
}
}
views.cbNSFW.isChecked = baseStatus.sensitive == true
// 再編集の場合はdefault_textは反映されない
val decodeOptions = DecodeOptions(
this,
mentionFullAcct = true,
mentions = baseStatus.mentions,
mentionDefaultHostDomain = account
)
var text: CharSequence = if (account.isMisskey) {
baseStatus.content ?: ""
} else {
decodeOptions.decodeHTML(baseStatus.content)
}
views.etContent.setText(text)
views.etContent.setSelection(text.length)
text = decodeOptions.decodeEmoji(baseStatus.spoiler_text)
views.etContentWarning.setText(text)
views.etContentWarning.setSelection(text.length)
views.cbContentWarning.isChecked = text.isNotEmpty()
val srcEnquete = baseStatus.enquete
val srcItems = srcEnquete?.items
when {
srcItems == null -> {
//
}
srcEnquete.pollType == TootPollsType.FriendsNico &&
srcEnquete.type != TootPolls.TYPE_ENQUETE -> {
// フレニコAPIのアンケート結果は再編集の対象外
}
else -> {
views.spPollType.setSelection(
if (srcEnquete.pollType == TootPollsType.FriendsNico) {
2
} else {
1
}
)
text = decodeOptions.decodeHTML(srcEnquete.question)
views.etContent.text = text
views.etContent.setSelection(text.length)
var srcIndex = 0
for (et in etChoices) {
if (srcIndex < srcItems.size) {
val choice = srcItems[srcIndex]
when {
srcIndex == srcItems.size - 1 && choice.text == "\uD83E\uDD14" -> {
// :thinking_face: は再現しない
}
else -> {
et.setText(decodeOptions.decodeEmoji(choice.text))
++srcIndex
continue
}
}
}
et.setText("")
}
}
}
} catch (ex: Throwable) {
log.trace(ex)
}
}

View File

@ -98,6 +98,7 @@ fun ActPost.resetText() {
resetMushroom()
states.redraftStatusId = null
states.editStatusId = null
states.timeSchedule = 0L
attachmentPicker.reset()
scheduledStatus = null
@ -194,6 +195,10 @@ fun ActPost.updateText(
intent.getStringExtra(ActPost.KEY_REDRAFT_STATUS)
?.let { initializeFromRedraftStatus(account, it) }
// 再編集
intent.getStringExtra(ActPost.KEY_EDIT_STATUS)
?.let { initializeFromEditStatus(account, it) }
// 予約編集の再編集
intent.getStringExtra(ActPost.KEY_SCHEDULED_STATUS)
?.let { initializeFromScheduledStatus(account, it) }
@ -329,6 +334,7 @@ fun ActPost.performPost() {
scheduledAt = states.timeSchedule,
scheduledId = scheduledStatus?.id,
redraftStatusId = states.redraftStatusId,
editStatusId = states.editStatusId,
emojiMapCustom = App1.custom_emoji_lister.getMap(account),
useQuoteToot = views.cbQuote.isChecked,
callback = object : PostCompleteCallback {
@ -338,6 +344,9 @@ fun ActPost.performPost() {
status.id.putTo(data, ActPost.EXTRA_POSTED_STATUS_ID)
states.redraftStatusId?.putTo(data, ActPost.EXTRA_POSTED_REDRAFT_ID)
status.in_reply_to_id?.putTo(data, ActPost.EXTRA_POSTED_REPLY_ID)
if (states.editStatusId != null) {
data.putExtra(ActPost.KEY_EDIT_STATUS, status.json.toString())
}
ActMain.refActMain?.get()?.onCompleteActPost(data)
if (isMultiWindowPost) {

View File

@ -31,6 +31,9 @@ data class ActPostStates(
@Serializable(with = EntityIdSerializer::class)
var redraftStatusId: EntityId? = null,
@Serializable(with = EntityIdSerializer::class)
var editStatusId: EntityId? = null,
var mushroomInput: Int = 0,
var mushroomStart: Int = 0,
var mushroomEnd: Int = 0,

View File

@ -267,6 +267,7 @@ class TootInstance(parser: TootParser, src: JsonObject) {
val VERSION_3_1_3 = VersionString("3.1.3")
val VERSION_3_3_0_rc1 = VersionString("3.3.0rc1")
val VERSION_3_4_0_rc1 = VersionString("3.4.0rc1")
val VERSION_3_5_0_rc1 = VersionString("3.5.0rc1")
val MISSKEY_VERSION_11 = VersionString("11.0")
val MISSKEY_VERSION_12 = VersionString("12.0")

View File

@ -42,13 +42,20 @@ class TootNotification(parser: TootParser, src: JsonObject) : TimelineItem() {
const val TYPE_POLL = "poll"
const val TYPE_STATUS = "status"
// (Mastodon 3.5.0rc1)
const val TYPE_UPDATE = "update"
}
val json: JsonObject
val id: EntityId
val type: String // One of: "mention", "reblog", "favourite", "follow"
val accountRef: TootAccountRef? // The Account sending the notification to the user
val status: TootStatus? // The Status associated with the notification, if applicable
// The Status associated with the notification, if applicable
// 投稿の更新により変更可能になる
var status: TootStatus?
var reaction: TootReaction? = null
val reblog_visibility: TootVisibility

View File

@ -125,8 +125,9 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
// null or the ID of the account it replies to
val in_reply_to_account_id: EntityId?
// null or the reblogged Status
val reblog: TootStatus?
// null or the reblogged Status
// 投稿の更新が実装されたのでvarになった
var reblog: TootStatus? = null
//One of: public, unlisted, private, direct
val visibility: TootVisibility

View File

@ -800,6 +800,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
colorAlpha(PrefI.ipEventBgColorQuote, R.string.quote_renote)
colorAlpha(PrefI.ipEventBgColorVote, R.string.vote_polls)
colorAlpha(PrefI.ipEventBgColorStatus, R.string.status)
colorAlpha(PrefI.ipEventBgColorUpdate, R.string.notification_type_update)
colorAlpha(
PrefI.ipConversationMainTootBgColor,

View File

@ -1,5 +1,6 @@
package jp.juggler.subwaytooter.column
import jp.juggler.subwaytooter.api.TootParser
import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.columnviewholder.onListListUpdated
import jp.juggler.subwaytooter.notification.PollingWorker
@ -8,6 +9,7 @@ import jp.juggler.subwaytooter.util.BucketList
import jp.juggler.subwaytooter.util.matchHost
import jp.juggler.util.AdapterChange
import jp.juggler.util.AdapterChangeType
import jp.juggler.util.JsonObject
import jp.juggler.util.LogCategory
import java.util.*
import kotlin.collections.ArrayList
@ -239,6 +241,40 @@ fun Column.onMuteUpdated() {
}
}
fun Column.replaceStatus(statusId: EntityId, statusJson: JsonObject) {
fun createStatus() =
TootParser(context, accessInfo).status(statusJson)
?: error("replaceStatus: parse failed.")
val tmpList = ArrayList(listData)
var changed = false
for (i in 0 until tmpList.size) {
when (val item = tmpList[i]) {
is TootStatus -> {
if (item.id == statusId) {
tmpList[i] = createStatus()
changed = true
} else if (item.reblog?.id == statusId) {
item.reblog = createStatus().also { it.reblogParent = item }
changed = true
}
}
is TootNotification -> {
if (item.status?.id == statusId) {
item.status = createStatus()
changed = true
}
}
}
}
if (changed) {
listData.clear()
listData.addAll(tmpList)
fireShowContent(reason = "replaceStatus")
}
}
fun Column.onHideFavouriteNotification(acct: Acct) {
if (!isNotificationColumn) return

View File

@ -18,18 +18,18 @@ private val log = LogCategory("ColumnFilters")
val Column.isFilterEnabled: Boolean
get() = withAttachment ||
withHighlight ||
regexText.isNotEmpty() ||
dontShowNormalToot ||
dontShowNonPublicToot ||
quickFilter != Column.QUICK_FILTER_ALL ||
dontShowBoost ||
dontShowFavourite ||
dontShowFollow ||
dontShowReply ||
dontShowReaction ||
dontShowVote ||
(languageFilter?.isNotEmpty() == true)
withHighlight ||
regexText.isNotEmpty() ||
dontShowNormalToot ||
dontShowNonPublicToot ||
quickFilter != Column.QUICK_FILTER_ALL ||
dontShowBoost ||
dontShowFavourite ||
dontShowFollow ||
dontShowReply ||
dontShowReaction ||
dontShowVote ||
(languageFilter?.isNotEmpty() == true)
// マストドン2.4.3rcのキーワードフィルタのコンテキスト
fun Column.getFilterContext() = when (type) {
@ -66,7 +66,7 @@ fun Column.canNSFWDefault(): Boolean = canStatusFilter()
fun Column.canFilterBoost(): Boolean = when (type) {
ColumnType.HOME, ColumnType.MISSKEY_HYBRID, ColumnType.PROFILE,
ColumnType.NOTIFICATIONS, ColumnType.NOTIFICATION_FROM_ACCT,
ColumnType.LIST_TL, ColumnType.MISSKEY_ANTENNA_TL
ColumnType.LIST_TL, ColumnType.MISSKEY_ANTENNA_TL,
-> true
ColumnType.LOCAL, ColumnType.FEDERATE, ColumnType.HASHTAG, ColumnType.SEARCH -> isMisskey
ColumnType.HASHTAG_FROM_ACCT -> false
@ -78,7 +78,7 @@ fun Column.canFilterBoost(): Boolean = when (type) {
fun Column.canFilterReply(): Boolean = when (type) {
ColumnType.HOME, ColumnType.MISSKEY_HYBRID, ColumnType.PROFILE,
ColumnType.NOTIFICATIONS, ColumnType.NOTIFICATION_FROM_ACCT,
ColumnType.LIST_TL, ColumnType.MISSKEY_ANTENNA_TL, ColumnType.DIRECT_MESSAGES
ColumnType.LIST_TL, ColumnType.MISSKEY_ANTENNA_TL, ColumnType.DIRECT_MESSAGES,
-> true
ColumnType.LOCAL, ColumnType.FEDERATE, ColumnType.HASHTAG, ColumnType.SEARCH -> isMisskey
ColumnType.HASHTAG_FROM_ACCT -> true
@ -88,7 +88,7 @@ fun Column.canFilterReply(): Boolean = when (type) {
fun Column.canFilterNormalToot(): Boolean = when (type) {
ColumnType.NOTIFICATIONS -> true
ColumnType.HOME, ColumnType.MISSKEY_HYBRID,
ColumnType.LIST_TL, ColumnType.MISSKEY_ANTENNA_TL
ColumnType.LIST_TL, ColumnType.MISSKEY_ANTENNA_TL,
-> true
ColumnType.LOCAL, ColumnType.FEDERATE, ColumnType.HASHTAG, ColumnType.SEARCH -> isMisskey
ColumnType.HASHTAG_FROM_ACCT -> true
@ -97,7 +97,7 @@ fun Column.canFilterNormalToot(): Boolean = when (type) {
fun Column.canFilterNonPublicToot(): Boolean = when (type) {
ColumnType.HOME, ColumnType.MISSKEY_HYBRID,
ColumnType.LIST_TL, ColumnType.MISSKEY_ANTENNA_TL
ColumnType.LIST_TL, ColumnType.MISSKEY_ANTENNA_TL,
-> true
ColumnType.LOCAL, ColumnType.FEDERATE, ColumnType.HASHTAG, ColumnType.SEARCH -> isMisskey
ColumnType.HASHTAG_FROM_ACCT -> true
@ -245,26 +245,34 @@ fun Column.isFiltered(item: TootNotification): Boolean {
TootNotification.TYPE_REBLOG,
TootNotification.TYPE_RENOTE,
TootNotification.TYPE_QUOTE -> dontShowBoost
TootNotification.TYPE_QUOTE,
-> dontShowBoost
TootNotification.TYPE_FOLLOW,
TootNotification.TYPE_UNFOLLOW,
TootNotification.TYPE_FOLLOW_REQUEST,
TootNotification.TYPE_FOLLOW_REQUEST_MISSKEY,
TootNotification.TYPE_FOLLOW_REQUEST_ACCEPTED_MISSKEY -> dontShowFollow
TootNotification.TYPE_FOLLOW_REQUEST_ACCEPTED_MISSKEY,
-> dontShowFollow
TootNotification.TYPE_MENTION,
TootNotification.TYPE_REPLY -> dontShowReply
TootNotification.TYPE_REPLY,
-> dontShowReply
TootNotification.TYPE_EMOJI_REACTION_PLEROMA,
TootNotification.TYPE_EMOJI_REACTION,
TootNotification.TYPE_REACTION -> dontShowReaction
TootNotification.TYPE_REACTION,
-> dontShowReaction
TootNotification.TYPE_VOTE,
TootNotification.TYPE_POLL,
TootNotification.TYPE_POLL_VOTE_MISSKEY -> dontShowVote
TootNotification.TYPE_POLL_VOTE_MISSKEY,
-> dontShowVote
TootNotification.TYPE_STATUS -> dontShowNormalToot
TootNotification.TYPE_UPDATE -> dontShowNormalToot && dontShowBoost
else -> false
}
@ -272,26 +280,33 @@ fun Column.isFiltered(item: TootNotification): Boolean {
TootNotification.TYPE_FAVOURITE -> quickFilter != Column.QUICK_FILTER_FAVOURITE
TootNotification.TYPE_REBLOG,
TootNotification.TYPE_RENOTE,
TootNotification.TYPE_QUOTE -> quickFilter != Column.QUICK_FILTER_BOOST
TootNotification.TYPE_QUOTE,
-> quickFilter != Column.QUICK_FILTER_BOOST
TootNotification.TYPE_FOLLOW,
TootNotification.TYPE_UNFOLLOW,
TootNotification.TYPE_FOLLOW_REQUEST,
TootNotification.TYPE_FOLLOW_REQUEST_MISSKEY,
TootNotification.TYPE_FOLLOW_REQUEST_ACCEPTED_MISSKEY -> quickFilter != Column.QUICK_FILTER_FOLLOW
TootNotification.TYPE_FOLLOW_REQUEST_ACCEPTED_MISSKEY,
-> quickFilter != Column.QUICK_FILTER_FOLLOW
TootNotification.TYPE_MENTION,
TootNotification.TYPE_REPLY -> quickFilter != Column.QUICK_FILTER_MENTION
TootNotification.TYPE_REPLY,
-> quickFilter != Column.QUICK_FILTER_MENTION
TootNotification.TYPE_EMOJI_REACTION_PLEROMA,
TootNotification.TYPE_EMOJI_REACTION,
TootNotification.TYPE_REACTION -> quickFilter != Column.QUICK_FILTER_REACTION
TootNotification.TYPE_REACTION,
-> quickFilter != Column.QUICK_FILTER_REACTION
TootNotification.TYPE_VOTE,
TootNotification.TYPE_POLL,
TootNotification.TYPE_POLL_VOTE_MISSKEY -> quickFilter != Column.QUICK_FILTER_VOTE
TootNotification.TYPE_POLL_VOTE_MISSKEY,
-> quickFilter != Column.QUICK_FILTER_VOTE
TootNotification.TYPE_STATUS -> quickFilter != Column.QUICK_FILTER_POST
TootNotification.TYPE_UPDATE -> quickFilter != Column.QUICK_FILTER_POST
else -> true
}
}
@ -330,7 +345,8 @@ fun Column.isFiltered(item: TootNotification): Boolean {
TootNotification.TYPE_FOLLOW,
TootNotification.TYPE_FOLLOW_REQUEST,
TootNotification.TYPE_FOLLOW_REQUEST_MISSKEY,
TootNotification.TYPE_FOLLOW_REQUEST_ACCEPTED_MISSKEY -> {
TootNotification.TYPE_FOLLOW_REQUEST_ACCEPTED_MISSKEY,
-> {
val who = item.account
if (who != null && favMuteSet?.contains(accessInfo.getFullAcct(who)) == true) {
log.d("${accessInfo.getFullAcct(who)} is in favMuteSet.")

View File

@ -60,8 +60,9 @@ import jp.juggler.util.LogCategory
// 2021/5/23 60=>61 SavedAccountテーブルに項目追加
// 2021/11/21 61=>62 SavedAccountテーブルに項目追加
// 2022/1/5 62=>63 SavedAccountテーブルに項目追加
// 2022/3/15 63=>64 SavedAccountテーブルに項目追加
const val DB_VERSION = 63
const val DB_VERSION = 64
const val DB_NAME = "app_db"
val TABLE_LIST = arrayOf(

View File

@ -7,16 +7,20 @@ import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.*
import androidx.annotation.IdRes
import android.widget.Button
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.widget.AppCompatButton
import androidx.core.content.ContextCompat
import jp.juggler.subwaytooter.*
import jp.juggler.subwaytooter.ActMain
import jp.juggler.subwaytooter.ActText
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.action.*
import jp.juggler.subwaytooter.actmain.nextPosition
import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.column.Column
import jp.juggler.subwaytooter.column.ColumnType
import jp.juggler.subwaytooter.databinding.DlgContextMenuBinding
import jp.juggler.subwaytooter.dialog.DlgListMember
import jp.juggler.subwaytooter.dialog.DlgQRCode
import jp.juggler.subwaytooter.pref.PrefB
@ -50,21 +54,22 @@ internal class DlgContextMenu(
private val dialog: Dialog
private val viewRoot = activity.layoutInflater.inflate(R.layout.dlg_context_menu, null, false)
private fun <T : View> fv(@IdRes id: Int): T = viewRoot.findViewById(id)
private val views = DlgContextMenuBinding.inflate(activity.layoutInflater)
private val btnGroupStatusCrossAccount: Button = fv(R.id.btnGroupStatusCrossAccount)
private val llGroupStatusCrossAccount: View = fv(R.id.llGroupStatusCrossAccount)
private val btnGroupStatusAround: Button = fv(R.id.btnGroupStatusAround)
private val llGroupStatusAround: View = fv(R.id.llGroupStatusAround)
private val btnGroupStatusByMe: Button = fv(R.id.btnGroupStatusByMe)
private val llGroupStatusByMe: View = fv(R.id.llGroupStatusByMe)
private val btnGroupStatusExtra: Button = fv(R.id.btnGroupStatusExtra)
private val llGroupStatusExtra: View = fv(R.id.llGroupStatusExtra)
private val btnGroupUserCrossAccount: Button = fv(R.id.btnGroupUserCrossAccount)
private val llGroupUserCrossAccount: View = fv(R.id.llGroupUserCrossAccount)
private val btnGroupUserExtra: Button = fv(R.id.btnGroupUserExtra)
private val llGroupUserExtra: View = fv(R.id.llGroupUserExtra)
// private fun <T : View> fv(@IdRes id: Int): T = viewRoot.findViewById(id)
//
// private val btnGroupStatusCrossAccount: Button = fv(R.id.btnGroupStatusCrossAccount)
// private val llGroupStatusCrossAccount: View = fv(R.id.llGroupStatusCrossAccount)
// private val btnGroupStatusAround: Button = fv(R.id.btnGroupStatusAround)
// private val llGroupStatusAround: View = fv(R.id.llGroupStatusAround)
// private val btnGroupStatusByMe: Button = fv(R.id.btnGroupStatusByMe)
// private val llGroupStatusByMe: View = fv(R.id.llGroupStatusByMe)
// private val btnGroupStatusExtra: Button = fv(R.id.btnGroupStatusExtra)
// private val llGroupStatusExtra: View = fv(R.id.llGroupStatusExtra)
// private val btnGroupUserCrossAccount: Button = fv(R.id.btnGroupUserCrossAccount)
// private val llGroupUserCrossAccount: View = fv(R.id.llGroupUserCrossAccount)
// private val btnGroupUserExtra: Button = fv(R.id.btnGroupUserExtra)
// private val llGroupUserExtra: View = fv(R.id.llGroupUserExtra)
init {
val columnType = column.type
@ -79,147 +84,87 @@ internal class DlgContextMenu(
}
this.dialog = Dialog(activity)
dialog.setContentView(viewRoot)
dialog.setContentView(views.root)
dialog.setCancelable(true)
dialog.setCanceledOnTouchOutside(true)
val btnAccountWebPage: View = fv(R.id.btnAccountWebPage)
val btnAroundAccountTL: View = fv(R.id.btnAroundAccountTL)
val btnAroundFTL: View = fv(R.id.btnAroundFTL)
val btnAroundLTL: View = fv(R.id.btnAroundLTL)
val btnAvatarImage: View = fv(R.id.btnAvatarImage)
val btnBlock: ImageView = fv(R.id.btnBlock)
val btnBookmarkAnotherAccount: View = fv(R.id.btnBookmarkAnotherAccount)
val btnBoostAnotherAccount: View = fv(R.id.btnBoostAnotherAccount)
val btnBoostedBy: View = fv(R.id.btnBoostedBy)
val btnBoostWithVisibility: Button = fv(R.id.btnBoostWithVisibility)
val btnConversationAnotherAccount: View = fv(R.id.btnConversationAnotherAccount)
val btnConversationMute: Button = fv(R.id.btnConversationMute)
val btnCopyAccountId: Button = fv(R.id.btnCopyAccountId)
val btnDelete: View = fv(R.id.btnDelete)
val btnDeleteSuggestion: View = fv(R.id.btnDeleteSuggestion)
val btnDomainBlock: Button = fv(R.id.btnDomainBlock)
val btnDomainTimeline: View = fv(R.id.btnDomainTimeline)
val btnEndorse: Button = fv(R.id.btnEndorse)
val btnFavouriteAnotherAccount: View = fv(R.id.btnFavouriteAnotherAccount)
val btnFavouritedBy: View = fv(R.id.btnFavouritedBy)
val btnFollow: ImageButton = fv(R.id.btnFollow)
val btnFollowFromAnotherAccount: View = fv(R.id.btnFollowFromAnotherAccount)
val btnFollowRequestNG: View = fv(R.id.btnFollowRequestNG)
val btnFollowRequestOK: View = fv(R.id.btnFollowRequestOK)
val btnHideBoost: View = fv(R.id.btnHideBoost)
val btnHideFavourite: View = fv(R.id.btnHideFavourite)
val btnInstanceInformation: Button = fv(R.id.btnInstanceInformation)
val btnListMemberAddRemove: View = fv(R.id.btnListMemberAddRemove)
val btnMute: ImageView = fv(R.id.btnMute)
val btnMuteApp: Button = fv(R.id.btnMuteApp)
val btnNotificationDelete: View = fv(R.id.btnNotificationDelete)
val btnNotificationFrom: Button = fv(R.id.btnNotificationFrom)
val btnOpenAccountInAdminWebUi: Button = fv(R.id.btnOpenAccountInAdminWebUi)
val btnOpenInstanceInAdminWebUi: Button = fv(R.id.btnOpenInstanceInAdminWebUi)
val btnOpenProfileFromAnotherAccount: View = fv(R.id.btnOpenProfileFromAnotherAccount)
val btnOpenTimeline: Button = fv(R.id.btnOpenTimeline)
val btnProfile: View = fv(R.id.btnProfile)
val btnProfileDirectory: Button = fv(R.id.btnProfileDirectory)
val btnProfilePin: View = fv(R.id.btnProfilePin)
val btnProfileUnpin: View = fv(R.id.btnProfileUnpin)
val btnQuoteAnotherAccount: View = fv(R.id.btnQuoteAnotherAccount)
val btnQuoteTootBT: View = fv(R.id.btnQuoteTootBT)
val btnReactionAnotherAccount: View = fv(R.id.btnReactionAnotherAccount)
val btnRedraft: View = fv(R.id.btnRedraft)
val btnReplyAnotherAccount: View = fv(R.id.btnReplyAnotherAccount)
val btnReportStatus: View = fv(R.id.btnReportStatus)
val btnReportUser: View = fv(R.id.btnReportUser)
val btnSendMessage: View = fv(R.id.btnSendMessage)
val btnSendMessageFromAnotherAccount: View = fv(R.id.btnSendMessageFromAnotherAccount)
val btnShowBoost: View = fv(R.id.btnShowBoost)
val btnShowFavourite: View = fv(R.id.btnShowFavourite)
val btnStatusWebPage: View = fv(R.id.btnStatusWebPage)
val btnText: View = fv(R.id.btnText)
val ivFollowedBy: ImageView = fv(R.id.ivFollowedBy)
val llAccountActionBar: View = fv(R.id.llAccountActionBar)
val llLinks: LinearLayout = fv(R.id.llLinks)
val llNotification: View = fv(R.id.llNotification)
val llStatus: View = fv(R.id.llStatus)
val btnStatusNotification: Button = fv(R.id.btnStatusNotification)
arrayOf(
btnAccountWebPage,
btnAroundAccountTL,
btnAroundFTL,
btnAroundLTL,
btnAvatarImage,
btnBlock,
btnBookmarkAnotherAccount,
btnBoostAnotherAccount,
btnBoostedBy,
btnBoostWithVisibility,
btnConversationAnotherAccount,
btnConversationMute,
btnCopyAccountId,
btnDelete,
btnDeleteSuggestion,
btnDomainBlock,
btnDomainTimeline,
btnEndorse,
btnFavouriteAnotherAccount,
btnFavouritedBy,
btnFollow,
btnFollowFromAnotherAccount,
btnFollowRequestNG,
btnFollowRequestOK,
btnHideBoost,
btnHideFavourite,
btnInstanceInformation,
btnListMemberAddRemove,
btnMute,
btnMuteApp,
btnNotificationDelete,
btnNotificationFrom,
btnOpenAccountInAdminWebUi,
btnOpenInstanceInAdminWebUi,
btnOpenProfileFromAnotherAccount,
btnOpenTimeline,
btnProfile,
btnProfileDirectory,
btnProfilePin,
btnProfileUnpin,
btnQuoteAnotherAccount,
btnQuoteTootBT,
btnReactionAnotherAccount,
btnRedraft,
btnReplyAnotherAccount,
btnReportStatus,
btnReportUser,
btnSendMessage,
btnSendMessageFromAnotherAccount,
btnShowBoost,
btnShowFavourite,
btnStatusNotification,
btnStatusWebPage,
btnText,
views.btnAccountWebPage,
views.btnAroundAccountTL,
views.btnAroundFTL,
views.btnAroundLTL,
views.btnAvatarImage,
views.btnBlock,
views.btnBookmarkAnotherAccount,
views.btnBoostAnotherAccount,
views.btnBoostedBy,
views.btnBoostWithVisibility,
views.btnConversationAnotherAccount,
views.btnConversationMute,
views.btnCopyAccountId,
views.btnDelete,
views.btnDeleteSuggestion,
views.btnDomainBlock,
views.btnDomainTimeline,
views.btnEndorse,
views.btnFavouriteAnotherAccount,
views.btnFavouritedBy,
views.btnFollow,
views.btnFollowFromAnotherAccount,
views.btnFollowRequestNG,
views.btnFollowRequestOK,
views.btnHideBoost,
views.btnHideFavourite,
views.btnInstanceInformation,
views.btnListMemberAddRemove,
views.btnMute,
views.btnMuteApp,
views.btnNotificationDelete,
views.btnNotificationFrom,
views.btnOpenAccountInAdminWebUi,
views.btnOpenInstanceInAdminWebUi,
views.btnOpenProfileFromAnotherAccount,
views.btnOpenTimeline,
views.btnProfile,
views.btnProfileDirectory,
views.btnProfilePin,
views.btnProfileUnpin,
views.btnQuoteAnotherAccount,
views.btnQuoteTootBT,
views.btnReactionAnotherAccount,
views.btnRedraft,
views.btnStatusEdit,
views.btnReplyAnotherAccount,
views.btnReportStatus,
views.btnReportUser,
views.btnSendMessage,
views.btnSendMessageFromAnotherAccount,
views.btnShowBoost,
views.btnShowFavourite,
views.btnStatusNotification,
views.btnStatusWebPage,
views.btnText,
viewRoot.findViewById(R.id.btnQuoteUrlStatus),
viewRoot.findViewById(R.id.btnTranslate),
viewRoot.findViewById(R.id.btnQuoteUrlAccount),
viewRoot.findViewById(R.id.btnShareUrlStatus),
viewRoot.findViewById(R.id.btnShareUrlAccount),
viewRoot.findViewById(R.id.btnQuoteName)
views.btnQuoteUrlStatus,
views.btnTranslate,
views.btnQuoteUrlAccount,
views.btnShareUrlStatus,
views.btnShareUrlAccount,
views.btnQuoteName
).forEach { it.setOnClickListener(this) }
arrayOf(
btnBlock,
btnFollow,
btnMute,
btnProfile,
btnQuoteAnotherAccount,
btnQuoteTootBT,
btnSendMessage,
views.btnBlock,
views.btnFollow,
views.btnMute,
views.btnProfile,
views.btnQuoteAnotherAccount,
views.btnQuoteTootBT,
views.btnSendMessage,
).forEach { it.setOnLongClickListener(this) }
val accountList = SavedAccount.loadAccountList(activity)
// final ArrayList< SavedAccount > account_list_non_pseudo_same_instance = new ArrayList<>();
val accountListNonPseudo = ArrayList<SavedAccount>()
for (a in accountList) {
@ -232,8 +177,8 @@ internal class DlgContextMenu(
}
if (status == null) {
llStatus.visibility = View.GONE
llLinks.visibility = View.GONE
views.llStatus.visibility = View.GONE
views.llLinks.visibility = View.GONE
} else {
val statusByMe = accessInfo.isMe(status.account)
@ -262,7 +207,7 @@ internal class DlgContextMenu(
dialog.dismissSafe()
span.onClick(contentTextView)
}
llLinks.addView(b, insPos++)
views.llLinks.addView(b, insPos++)
}
val dc = status.decoded_content
@ -274,26 +219,26 @@ internal class DlgContextMenu(
}
}
}
llLinks.vg(llLinks.childCount > 1)
views.llLinks.vg(views.llLinks.childCount > 1)
btnGroupStatusByMe.vg(statusByMe)
views.btnGroupStatusByMe.vg(statusByMe)
btnQuoteTootBT.vg(status.reblogParent != null)
views.btnQuoteTootBT.vg(status.reblogParent != null)
btnBoostWithVisibility.vg(!accessInfo.isPseudo && !accessInfo.isMisskey)
views.btnBoostWithVisibility.vg(!accessInfo.isPseudo && !accessInfo.isMisskey)
btnReportStatus.vg(!(statusByMe || accessInfo.isPseudo))
views.btnReportStatus.vg(!(statusByMe || accessInfo.isPseudo))
val applicationName = status.application?.name
if (statusByMe || applicationName == null || applicationName.isEmpty()) {
btnMuteApp.visibility = View.GONE
views.btnMuteApp.visibility = View.GONE
} else {
btnMuteApp.text = activity.getString(R.string.mute_app_of, applicationName)
views.btnMuteApp.text = activity.getString(R.string.mute_app_of, applicationName)
}
val canPin = status.canPin(accessInfo)
btnProfileUnpin.vg(canPin && status.pinned)
btnProfilePin.vg(canPin && !status.pinned)
views.btnProfileUnpin.vg(canPin && status.pinned)
views.btnProfilePin.vg(canPin && !status.pinned)
}
val bShowConversationMute = when {
@ -304,7 +249,7 @@ internal class DlgContextMenu(
}
val muted = status?.muted ?: false
btnConversationMute.vg(bShowConversationMute)
views.btnConversationMute.vg(bShowConversationMute)
?.setText(
when {
muted -> R.string.unmute_this_conversation
@ -312,7 +257,7 @@ internal class DlgContextMenu(
}
)
llNotification.vg(notification != null)
views.llNotification.vg(notification != null)
val colorButtonAccent =
PrefI.ipButtonFollowingColor(activity.pref).notZero()
@ -329,11 +274,11 @@ internal class DlgContextMenu(
// 被フォロー状態
// Styler.setFollowIconとは異なり細かい状態を表示しない
ivFollowedBy.vg(relation.followed_by)
views.ivFollowedBy.vg(relation.followed_by)
// フォロー状態
// Styler.setFollowIconとは異なりミュートやブロックを表示しない
btnFollow.setImageResource(
views.btnFollow.setImageResource(
when {
relation.getRequested(who) -> R.drawable.ic_follow_wait
relation.getFollowing(who) -> R.drawable.ic_follow_cross
@ -341,7 +286,7 @@ internal class DlgContextMenu(
}
)
btnFollow.imageTintList = ColorStateList.valueOf(
views.btnFollow.imageTintList = ColorStateList.valueOf(
when {
relation.getRequested(who) -> colorButtonError
relation.getFollowing(who) -> colorButtonAccent
@ -350,7 +295,7 @@ internal class DlgContextMenu(
)
// ミュート状態
btnMute.imageTintList = ColorStateList.valueOf(
views.btnMute.imageTintList = ColorStateList.valueOf(
when (relation.muting) {
true -> colorButtonAccent
else -> colorButtonNormal
@ -358,7 +303,7 @@ internal class DlgContextMenu(
)
// ブロック状態
btnBlock.imageTintList = ColorStateList.valueOf(
views.btnBlock.imageTintList = ColorStateList.valueOf(
when (relation.blocking) {
true -> colorButtonAccent
else -> colorButtonNormal
@ -369,13 +314,13 @@ internal class DlgContextMenu(
if (accessInfo.isPseudo) {
// 疑似アカミュートができたのでアカウントアクションを表示する
showRelation(relation)
llAccountActionBar.visibility = View.VISIBLE
ivFollowedBy.vg(false)
btnFollow.setImageResource(R.drawable.ic_follow_plus)
btnFollow.imageTintList =
views.llAccountActionBar.visibility = View.VISIBLE
views.ivFollowedBy.vg(false)
views.btnFollow.setImageResource(R.drawable.ic_follow_plus)
views.btnFollow.imageTintList =
ColorStateList.valueOf(activity.attrColor(R.attr.colorImageButton))
btnNotificationFrom.visibility = View.GONE
views.btnNotificationFrom.visibility = View.GONE
} else {
showRelation(relation)
}
@ -383,20 +328,20 @@ internal class DlgContextMenu(
val whoApiHost = getUserApiHost()
val whoApDomain = getUserApDomain()
viewRoot.findViewById<View>(R.id.llInstance)
views.llInstance
.vg(whoApiHost.isValid)
?.let {
val tvInstanceActions: TextView = viewRoot.findViewById(R.id.tvInstanceActions)
val tvInstanceActions: TextView = views.tvInstanceActions
tvInstanceActions.text =
activity.getString(R.string.instance_actions_for, whoApDomain.pretty)
// 疑似アカウントではドメインブロックできない
// 自ドメインはブロックできない
btnDomainBlock.vg(
views.btnDomainBlock.vg(
!(accessInfo.isPseudo || accessInfo.matchHost(whoApiHost))
)
btnDomainTimeline.vg(
views.btnDomainTimeline.vg(
PrefB.bpEnableDomainTimeline(activity.pref) &&
!accessInfo.isPseudo &&
!accessInfo.isMisskey
@ -404,101 +349,100 @@ internal class DlgContextMenu(
}
if (who == null) {
btnCopyAccountId.visibility = View.GONE
btnOpenAccountInAdminWebUi.visibility = View.GONE
btnOpenInstanceInAdminWebUi.visibility = View.GONE
views.btnCopyAccountId.visibility = View.GONE
views.btnOpenAccountInAdminWebUi.visibility = View.GONE
views.btnOpenInstanceInAdminWebUi.visibility = View.GONE
btnReportUser.visibility = View.GONE
views.btnReportUser.visibility = View.GONE
} else {
btnCopyAccountId.visibility = View.VISIBLE
btnCopyAccountId.text = activity.getString(R.string.copy_account_id, who.id.toString())
views.btnCopyAccountId.visibility = View.VISIBLE
views.btnCopyAccountId.text =
activity.getString(R.string.copy_account_id, who.id.toString())
btnOpenAccountInAdminWebUi.vg(!accessInfo.isPseudo)
btnOpenInstanceInAdminWebUi.vg(!accessInfo.isPseudo)
views.btnOpenAccountInAdminWebUi.vg(!accessInfo.isPseudo)
views.btnOpenInstanceInAdminWebUi.vg(!accessInfo.isPseudo)
btnReportUser.vg(!(accessInfo.isPseudo || accessInfo.isMe(who)))
views.btnReportUser.vg(!(accessInfo.isPseudo || accessInfo.isMe(who)))
btnStatusNotification.vg(!accessInfo.isPseudo && accessInfo.isMastodon && relation.following)
?.let {
it.text = when (relation.notifying) {
true -> activity.getString(R.string.stop_notify_posts_from_this_user)
else -> activity.getString(R.string.notify_posts_from_this_user)
}
}
views.btnStatusNotification.vg(!accessInfo.isPseudo && accessInfo.isMastodon && relation.following)
?.text = when (relation.notifying) {
true -> activity.getString(R.string.stop_notify_posts_from_this_user)
else -> activity.getString(R.string.notify_posts_from_this_user)
}
}
viewRoot.findViewById<View>(R.id.btnAccountText).setOnClickListener(this)
views.btnAccountText.setOnClickListener(this)
if (accessInfo.isPseudo) {
btnProfile.visibility = View.GONE
btnSendMessage.visibility = View.GONE
btnEndorse.visibility = View.GONE
views.btnProfile.visibility = View.GONE
views.btnSendMessage.visibility = View.GONE
views.btnEndorse.visibility = View.GONE
}
btnEndorse.text = when (relation.endorsed) {
views.btnEndorse.text = when (relation.endorsed) {
false -> activity.getString(R.string.endorse_set)
else -> activity.getString(R.string.endorse_unset)
}
if (columnType != ColumnType.FOLLOW_REQUESTS) {
btnFollowRequestOK.visibility = View.GONE
btnFollowRequestNG.visibility = View.GONE
views.btnFollowRequestOK.visibility = View.GONE
views.btnFollowRequestNG.visibility = View.GONE
}
if (columnType != ColumnType.FOLLOW_SUGGESTION) {
btnDeleteSuggestion.visibility = View.GONE
views.btnDeleteSuggestion.visibility = View.GONE
}
if (accountListNonPseudo.isEmpty()) {
btnFollowFromAnotherAccount.visibility = View.GONE
btnSendMessageFromAnotherAccount.visibility = View.GONE
views.btnFollowFromAnotherAccount.visibility = View.GONE
views.btnSendMessageFromAnotherAccount.visibility = View.GONE
}
viewRoot.findViewById<View>(R.id.btnNickname).setOnClickListener(this)
viewRoot.findViewById<View>(R.id.btnCancel).setOnClickListener(this)
viewRoot.findViewById<View>(R.id.btnAccountQrCode).setOnClickListener(this)
views.btnNickname.setOnClickListener(this)
views.btnCancel.setOnClickListener(this)
views.btnAccountQrCode.setOnClickListener(this)
if (accessInfo.isPseudo ||
who == null ||
!relation.getFollowing(who) ||
relation.following_reblogs == UserRelation.REBLOG_UNKNOWN
) {
btnHideBoost.visibility = View.GONE
btnShowBoost.visibility = View.GONE
views.btnHideBoost.visibility = View.GONE
views.btnShowBoost.visibility = View.GONE
} else if (relation.following_reblogs == UserRelation.REBLOG_SHOW) {
btnHideBoost.visibility = View.VISIBLE
btnShowBoost.visibility = View.GONE
views.btnHideBoost.visibility = View.VISIBLE
views.btnShowBoost.visibility = View.GONE
} else {
btnHideBoost.visibility = View.GONE
btnShowBoost.visibility = View.VISIBLE
views.btnHideBoost.visibility = View.GONE
views.btnShowBoost.visibility = View.VISIBLE
}
when {
who == null -> {
btnHideFavourite.visibility = View.GONE
btnShowFavourite.visibility = View.GONE
views.btnHideFavourite.visibility = View.GONE
views.btnShowFavourite.visibility = View.GONE
}
FavMute.contains(accessInfo.getFullAcct(who)) -> {
btnHideFavourite.visibility = View.GONE
btnShowFavourite.visibility = View.VISIBLE
views.btnHideFavourite.visibility = View.GONE
views.btnShowFavourite.visibility = View.VISIBLE
}
else -> {
btnHideFavourite.visibility = View.VISIBLE
btnShowFavourite.visibility = View.GONE
views.btnHideFavourite.visibility = View.VISIBLE
views.btnShowFavourite.visibility = View.GONE
}
}
btnListMemberAddRemove.visibility = View.VISIBLE
views.btnListMemberAddRemove.visibility = View.VISIBLE
updateGroup(btnGroupStatusCrossAccount, llGroupStatusCrossAccount)
updateGroup(btnGroupUserCrossAccount, llGroupUserCrossAccount)
updateGroup(btnGroupStatusAround, llGroupStatusAround)
updateGroup(btnGroupStatusByMe, llGroupStatusByMe)
updateGroup(btnGroupStatusExtra, llGroupStatusExtra)
updateGroup(btnGroupUserExtra, llGroupUserExtra)
updateGroup(views.btnGroupStatusCrossAccount, views.llGroupStatusCrossAccount)
updateGroup(views.btnGroupUserCrossAccount, views.llGroupUserCrossAccount)
updateGroup(views.btnGroupStatusAround, views.llGroupStatusAround)
updateGroup(views.btnGroupStatusByMe, views.llGroupStatusByMe)
updateGroup(views.btnGroupStatusExtra, views.llGroupStatusExtra)
updateGroup(views.btnGroupUserExtra, views.llGroupUserExtra)
}
fun show() {
@ -557,34 +501,34 @@ internal class DlgContextMenu(
private fun onClickUpdateGroup(v: View): Boolean = when (v.id) {
R.id.btnGroupStatusCrossAccount -> updateGroup(
btnGroupStatusCrossAccount,
llGroupStatusCrossAccount,
views.btnGroupStatusCrossAccount,
views.llGroupStatusCrossAccount,
toggle = true
)
R.id.btnGroupUserCrossAccount -> updateGroup(
btnGroupUserCrossAccount,
llGroupUserCrossAccount,
views.btnGroupUserCrossAccount,
views.llGroupUserCrossAccount,
toggle = true
)
R.id.btnGroupStatusAround -> updateGroup(
btnGroupStatusAround,
llGroupStatusAround,
views.btnGroupStatusAround,
views.llGroupStatusAround,
toggle = true
)
R.id.btnGroupStatusByMe -> updateGroup(
btnGroupStatusByMe,
llGroupStatusByMe,
views.btnGroupStatusByMe,
views.llGroupStatusByMe,
toggle = true
)
R.id.btnGroupStatusExtra -> updateGroup(
btnGroupStatusExtra,
llGroupStatusExtra,
views.btnGroupStatusExtra,
views.llGroupStatusExtra,
toggle = true
)
R.id.btnGroupUserExtra -> updateGroup(
btnGroupUserExtra,
llGroupUserExtra,
views.btnGroupUserExtra,
views.llGroupUserExtra,
toggle = true
)
else -> false
@ -611,7 +555,7 @@ internal class DlgContextMenu(
v: View,
pos: Int,
who: TootAccount,
whoRef: TootAccountRef
whoRef: TootAccountRef,
): Boolean {
when (v.id) {
R.id.btnReportUser -> userReportForm(accessInfo, who)
@ -682,6 +626,7 @@ internal class DlgContextMenu(
R.id.btnConversationAnotherAccount -> conversationOtherInstance(pos, status)
R.id.btnDelete -> clickStatusDelete(accessInfo, status)
R.id.btnRedraft -> statusRedraft(accessInfo, status)
R.id.btnStatusEdit -> statusEdit(accessInfo, status)
R.id.btnMuteApp -> appMute(status.application)
R.id.btnBoostedBy -> clickBoostBy(pos, accessInfo, status, ColumnType.BOOSTED_BY)
R.id.btnFavouritedBy -> clickBoostBy(pos, accessInfo, status, ColumnType.FAVOURITED_BY)

View File

@ -217,7 +217,7 @@ fun ItemViewHolder.bind(
R.string.display_name_boosted_by,
boostStatus = item
)
showStatusOrReply(item.reblog, colorBg)
showStatusOrReply(reblog, colorBg)
}
}
}

View File

@ -49,6 +49,9 @@ fun ItemViewHolder.showNotification(n: TootNotification) {
TootNotification.TYPE_STATUS ->
showNotificationPost(n, nAccountRef, nStatus)
TootNotification.TYPE_UPDATE ->
showNotificationUpdate(n, nAccountRef, nStatus)
TootNotification.TYPE_FOLLOW_REQUEST,
TootNotification.TYPE_FOLLOW_REQUEST_MISSKEY,
-> showNotificationFollowRequest(n, nAccountRef)
@ -153,6 +156,17 @@ private fun ItemViewHolder.showNotificationPost(
nStatus?.let { showNotificationStatus(it, colorBg) }
}
private fun ItemViewHolder.showNotificationUpdate(
n: TootNotification,
nAccountRef: TootAccountRef?,
nStatus: TootStatus?
) {
val colorBg = PrefI.ipEventBgColorUpdate(activity.pref)
val iconId = R.drawable.ic_refresh
nAccountRef?.let { showBoost(it, n.time_created_at, iconId, R.string.display_name_updates_post) }
nStatus?.let { showNotificationStatus(it, colorBg) }
}
private fun ItemViewHolder.showNotificationReaction(
n: TootNotification,
nAccountRef: TootAccountRef?,

View File

@ -51,7 +51,8 @@ class PushSubscriptionHelper(
account.notification_reaction.booleanToInt(16) +
account.notification_vote.booleanToInt(32) +
account.notification_follow_request.booleanToInt(64) +
account.notification_post.booleanToInt(128)
account.notification_post.booleanToInt(128) +
account.notification_update.booleanToInt(256)
private val logBuffer = StringBuilder()
@ -321,6 +322,7 @@ class PushSubscriptionHelper(
put("poll", account.notification_vote)
put("follow_request", account.notification_follow_request)
put("status", account.notification_post)
put("update", account.notification_update)
put("emoji_reaction", account.notification_reaction) // fedibird拡張
}
@ -531,6 +533,7 @@ class PushSubscriptionHelper(
"status" -> ti.versionGE(TootInstance.VERSION_3_3_0_rc1)
"emoji_reaction" -> ti.versionGE(TootInstance.VERSION_3_4_0_rc1) &&
InstanceCapability.emojiReaction(account, ti)
"update" -> ti.versionGE(TootInstance.VERSION_3_5_0_rc1)
else -> {
log.w("${account.acct}: unknown alert '$it'. server version='${ti.version}'")

View File

@ -140,6 +140,9 @@ class TaskRunner(
TootNotification.TYPE_STATUS ->
context.getString(R.string.display_name_posted_by, name)
TootNotification.TYPE_UPDATE ->
context.getString(R.string.display_name_updates_post, name)
TootNotification.TYPE_FOLLOW ->
context.getString(R.string.display_name_followed_by, name)

View File

@ -95,6 +95,7 @@ object PrefI {
val ipEventBgColorVote = IntPref("EventBgColorVote", 0)
val ipEventBgColorFollowRequest = IntPref("EventBgColorFollowRequest", 0)
val ipEventBgColorStatus = IntPref("EventBgColorStatus", 0)
val ipEventBgColorUpdate = IntPref("EventBgColorUpdate", 0)
val ipEventBgColorGap = IntPref("EventBgColorGap", 0)

View File

@ -56,6 +56,7 @@ class SavedAccount(
var notification_reaction: Boolean = false
var notification_vote: Boolean = false
var notification_post: Boolean = false
var notification_update: Boolean = true
var sound_uri = ""
var confirm_follow: Boolean = false
@ -165,6 +166,7 @@ class SavedAccount(
notification_reaction = cursor.getBoolean(COL_NOTIFICATION_REACTION)
notification_vote = cursor.getBoolean(COL_NOTIFICATION_VOTE)
notification_post = cursor.getBoolean(COL_NOTIFICATION_POST)
notification_update = cursor.getBoolean(COL_NOTIFICATION_UPDATE)
dont_hide_nsfw = cursor.getBoolean(COL_DONT_HIDE_NSFW)
dont_show_timeout = cursor.getBoolean(COL_DONT_SHOW_TIMEOUT)
@ -247,6 +249,7 @@ class SavedAccount(
put(COL_NOTIFICATION_REACTION, notification_reaction)
put(COL_NOTIFICATION_VOTE, notification_vote)
put(COL_NOTIFICATION_POST, notification_post)
put(COL_NOTIFICATION_UPDATE, notification_update)
put(COL_CONFIRM_BOOST, confirm_boost)
put(COL_CONFIRM_FAVOURITE, confirm_favourite)
@ -327,6 +330,7 @@ class SavedAccount(
this.notification_reaction = b.notification_reaction
this.notification_vote = b.notification_vote
this.notification_post = b.notification_post
this.notification_update = b.notification_update
this.notification_tag = b.notification_tag
this.default_text = b.default_text
@ -423,6 +427,8 @@ class SavedAccount(
ColumnMeta(columnList, 33, "notification_vote", ColumnMeta.TS_TRUE)
private val COL_NOTIFICATION_POST =
ColumnMeta(columnList, 57, "notification_post", ColumnMeta.TS_TRUE)
private val COL_NOTIFICATION_UPDATE =
ColumnMeta(columnList, 64, "notification_update", ColumnMeta.TS_TRUE)
private val COL_CONFIRM_FOLLOW =
ColumnMeta(columnList, 10, "confirm_follow", ColumnMeta.TS_TRUE)
@ -546,6 +552,7 @@ class SavedAccount(
notification_reaction = false
notification_vote = false
notification_post = false
notification_update = false
}
}
@ -880,6 +887,8 @@ class SavedAccount(
TootNotification.TYPE_STATUS -> notification_post
TootNotification.TYPE_UPDATE -> notification_update
else -> false
}

View File

@ -48,6 +48,7 @@ class PostImpl(
val scheduledAt: Long,
val scheduledId: EntityId?,
val redraftStatusId: EntityId?,
val editStatusId: EntityId?,
val emojiMapCustom: HashMap<String, CustomEmoji>?,
var useQuoteToot: Boolean,
@ -548,8 +549,8 @@ class PostImpl(
return@runApiTask ex.result
}
// 元の投稿を削除する
if (redraftStatusId != null) {
// 元の投稿を削除する
deleteStatus(client)
} else if (scheduledId != null) {
deleteScheduledStatus(client)
@ -571,17 +572,34 @@ class PostImpl(
val bodyString = json.toString()
val requestBuilder = bodyString.toRequestBody(MEDIA_TYPE_JSON).toPost()
if (!PrefB.bpDontDuplicationCheck()) {
val digest = (bodyString + account.acct.ascii).digestSHA256Hex()
requestBuilder.header("Idempotency-Key", digest)
fun createRequestBuilder(isPut: Boolean = false): Request.Builder {
val requestBody = bodyString.toRequestBody(MEDIA_TYPE_JSON)
return when {
isPut -> Request.Builder().put(requestBody)
else -> Request.Builder().post(requestBody)
}.also {
if (!PrefB.bpDontDuplicationCheck()) {
val digest = (bodyString + account.acct.ascii).digestSHA256Hex()
it.header("Idempotency-Key", digest)
}
}
}
if (account.isMisskey) {
client.request("/api/notes/create", requestBuilder)
} else {
client.request("/api/v1/statuses", requestBuilder)
when {
account.isMisskey -> client.request(
"/api/notes/create",
createRequestBuilder()
)
editStatusId != null -> client.request(
"/api/v1/statuses/$editStatusId",
createRequestBuilder(isPut = true)
)
else -> client.request(
"/api/v1/statuses",
createRequestBuilder()
)
}?.also { result ->
val jsonObject = result.jsonObject

View File

@ -555,6 +555,10 @@
style="@style/setting_row_form"
android:text="@string/notification_type_post" />
<CheckBox
android:id="@+id/cbNotificationUpdate"
style="@style/setting_row_form"
android:text="@string/notification_type_update" />
<TextView
style="@style/setting_row_form"
android:layout_marginTop="12dp"

View File

@ -357,6 +357,20 @@
android:visibility="gone"
tools:ignore="RtlSymmetry">
<Button
android:id="@+id/btnStatusEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/btn_bg_transparent_round6dp"
android:gravity="start|center_vertical"
android:minHeight="32dp"
android:paddingStart="8dp"
android:paddingTop="4dp"
android:paddingEnd="8dp"
android:paddingBottom="4dp"
android:text="@string/status_edit"
android:textAllCaps="false" />
<Button
android:id="@+id/btnRedraft"
android:layout_width="match_parent"

View File

@ -102,6 +102,7 @@
\n\"鍵付き\"ユーザーの場合はフォローリクエストが承認されるまでお待ちください。そうでなければ2…3回再試行してください。</string>
<string name="cant_change_account_when_attachment_specified">添付ファイルがある状態ではアカウントを変更できません。</string>
<string name="cant_change_account_when_redraft">再編集中は投稿先アカウントを変更できません</string>
<string name="cant_change_account_when_edit">投稿の編集中は投稿先アカウントを変更できません</string>
<string name="cant_close_column_by_back_button_when_multiple_column_shown">複数カラムが表示されてると戻るボタンではカラムを閉じられません</string>
<string name="cant_follow_locked_user">鍵つきユーザをフォローできません</string>
<string name="cant_get_misskey_app_secret">このMisskeyサーバーのアプリ登録APIは応答にapp secretが含まれません。Misskeyをアップデートするようサーバー運営に相談してください。</string>
@ -206,6 +207,7 @@
<string name="display_name_followed_by">%1$sにフォローされました</string>
<string name="display_name_quoted_by">%1$sが引用しました</string>
<string name="display_name_posted_by">%1$sが投稿しました</string>
<string name="display_name_updates_post">%1$sが投稿を更新しました</string>
<string name="display_name_reaction_by">%1$sがリアクション</string>
<string name="display_name_replied_by">%1$sからの返信</string>
<string name="display_name_mentioned_by">%1$sからのメンション</string>
@ -501,6 +503,7 @@
<string name="notification_type_reaction">リアクション</string>
<string name="notification_type_vote">投票</string>
<string name="notification_type_post">投稿</string>
<string name="notification_type_update">投稿の更新</string>
<string name="notifications">通知</string>
<string name="nsfw">NSFW</string>
<string name="ok">OK</string>
@ -1123,4 +1126,5 @@
<string name="movie_transcode_max_square_pixels">最大平方ピクセル数(デフォルト=2304000。サーバからの指定でより少なくなる場合があります。)</string>
<string name="always">常に</string>
<string name="auto">自動</string>
<string name="status_edit">編集(Mastodon 3.5.0+)</string>
</resources>

View File

@ -91,6 +91,7 @@
<string name="display_name_follow_request_accepted_by">%1$s accepted your follow request</string>
<string name="display_name_quoted_by">%1$s quoted</string>
<string name="display_name_posted_by">%1$s posted</string>
<string name="display_name_updates_post">%1$s updates the post</string>
<string name="display_name_reaction_by">%1$s reactioned</string>
<string name="display_name_followed_by">%1$s is following you</string>
<string name="display_name_unfollowed_by">%1$s unfollowed you</string>
@ -637,6 +638,7 @@
<string name="notification_type_reaction">reaction</string>
<string name="notification_type_vote">vote</string>
<string name="notification_type_post">post</string>
<string name="notification_type_update">post update</string>
<string name="dont_show_normal_toot">Don\'t show normal toot</string>
<string name="dont_show_non_public_toot">Don\'t show non-public toot</string>
@ -699,6 +701,7 @@
<string name="redraft_and_delete">Redraft and delete</string>
<string name="cant_sync_toot">Can\'t synchronize toot.</string>
<string name="cant_change_account_when_redraft">Can\'t change account while using redraft.</string>
<string name="cant_change_account_when_edit">Can\'t change account while edit the post.</string>
<string name="delete_base_status_before_toot">Delete base status before posting. All of favourites/boosts will be lost. Replies will be disconnected. Media attachments will not kept on Mastodon pre-2.4.1 . Are you sure?</string>
<string name="notification_tl_font_size">Notification TL font size\n(Unit: sp. leave empty to default. app restart required)\n…\n…</string>
<string name="notification_tl_icon_size">Notification TL icon size (Unit:dp. default:24. app restart required)</string>
@ -1134,4 +1137,5 @@
<string name="movie_transcode_max_square_pixels">Max square pixels(default=2304000. Also it may be reduced by server configuration.)</string>
<string name="always">Always</string>
<string name="auto">Auto</string>
<string name="status_edit">Edit (Mastodon 3.5.0+)</string>
</resources>