リアクション動作の改善。絵文字描画時にrequestLayoutしすぎる問題の対策。
This commit is contained in:
parent
dd46422344
commit
9ba7e1d8d2
@ -6,6 +6,7 @@ import jp.juggler.subwaytooter.api.ApiTask
|
||||
import jp.juggler.subwaytooter.api.TootParser
|
||||
import jp.juggler.subwaytooter.api.entity.Host
|
||||
import jp.juggler.subwaytooter.api.entity.InstanceCapability
|
||||
import jp.juggler.subwaytooter.api.entity.TootInstance
|
||||
import jp.juggler.subwaytooter.api.entity.TootReaction
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus
|
||||
import jp.juggler.subwaytooter.api.runApiTask
|
||||
@ -18,17 +19,21 @@ import jp.juggler.subwaytooter.dialog.launchEmojiPicker
|
||||
import jp.juggler.subwaytooter.dialog.pickAccount
|
||||
import jp.juggler.subwaytooter.emoji.CustomEmoji
|
||||
import jp.juggler.subwaytooter.emoji.UnicodeEmoji
|
||||
import jp.juggler.subwaytooter.util.emojiSizeMode
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.accountListCanReaction
|
||||
import jp.juggler.subwaytooter.table.daoAcctColor
|
||||
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||
import jp.juggler.subwaytooter.util.DecodeOptions
|
||||
import jp.juggler.subwaytooter.util.emojiSizeMode
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.coroutine.launchMain
|
||||
import jp.juggler.util.data.encodePercent
|
||||
import jp.juggler.util.log.showToast
|
||||
import jp.juggler.util.network.*
|
||||
import jp.juggler.util.network.toDelete
|
||||
import jp.juggler.util.network.toFormRequestBody
|
||||
import jp.juggler.util.network.toPostRequestBuilder
|
||||
import jp.juggler.util.network.toPut
|
||||
import okhttp3.Request
|
||||
|
||||
private val rePleromaStatusUrl = """/objects/""".toRegex()
|
||||
|
||||
@ -51,10 +56,14 @@ fun ActMain.reactionAdd(
|
||||
) {
|
||||
val activity = this@reactionAdd
|
||||
val accessInfo = column.accessInfo
|
||||
val ti = TootInstance.getCached(accessInfo)
|
||||
|
||||
val canMultipleReaction = InstanceCapability.canMultipleReaction(accessInfo)
|
||||
val hasMyReaction = status.reactionSet?.hasMyReaction() == true
|
||||
if (hasMyReaction && !canMultipleReaction) {
|
||||
val maxReactionPerAccount = InstanceCapability.maxReactionPerAccount(accessInfo, ti)
|
||||
val myReactionCount = status.reactionSet?.myReactionCount ?: 0
|
||||
|
||||
if (maxReactionPerAccount <= 0) {
|
||||
return
|
||||
} else if (myReactionCount >= maxReactionPerAccount) {
|
||||
showToast(false, R.string.already_reactioned)
|
||||
return
|
||||
}
|
||||
@ -108,7 +117,11 @@ fun ActMain.reactionAdd(
|
||||
}
|
||||
}
|
||||
|
||||
if (canMultipleReaction && TootReaction.isCustomEmoji(code)) {
|
||||
if (TootReaction.isCustomEmoji(code) && !InstanceCapability.canCustomEmojiReaction(
|
||||
accessInfo,
|
||||
ti
|
||||
)
|
||||
) {
|
||||
showToast(false, "can't reaction with custom emoji from this account")
|
||||
return
|
||||
}
|
||||
@ -122,7 +135,7 @@ fun ActMain.reactionAdd(
|
||||
decodeEmoji = true,
|
||||
enlargeEmoji = DecodeOptions.emojiScaleReaction,
|
||||
enlargeCustomEmoji = DecodeOptions.emojiScaleReaction,
|
||||
emojiSizeMode = accessInfo.emojiSizeMode(),
|
||||
emojiSizeMode = accessInfo.emojiSizeMode(),
|
||||
)
|
||||
val emojiSpan = TootReaction.toSpannableStringBuilder(options, code, urlArg)
|
||||
confirm(
|
||||
@ -149,7 +162,7 @@ fun ActMain.reactionAdd(
|
||||
}.toPostRequestBuilder()
|
||||
) // 成功すると204 no content
|
||||
|
||||
canMultipleReaction -> client.request(
|
||||
ti?.pleromaFeatures != null -> client.request(
|
||||
"/api/v1/pleroma/statuses/${status.id}/reactions/${code.encodePercent("@")}",
|
||||
"".toFormRequestBody().toPut()
|
||||
)?.also { result ->
|
||||
@ -198,12 +211,11 @@ fun ActMain.reactionRemove(
|
||||
column: Column,
|
||||
status: TootStatus,
|
||||
reactionArg: TootReaction? = null,
|
||||
confirmed: Boolean = false,
|
||||
) {
|
||||
val activity = this
|
||||
val accessInfo = column.accessInfo
|
||||
|
||||
val canMultipleReaction = InstanceCapability.canMultipleReaction(accessInfo)
|
||||
val ti = TootInstance.getCached(accessInfo)
|
||||
|
||||
// 指定されたリアクションまたは自分がリアクションした最初のもの
|
||||
val reaction = reactionArg ?: status.reactionSet?.find { it.count > 0 && it.me }
|
||||
@ -213,19 +225,18 @@ fun ActMain.reactionRemove(
|
||||
}
|
||||
|
||||
launchAndShowError {
|
||||
|
||||
if (!confirmed) {
|
||||
val options = DecodeOptions(
|
||||
activity,
|
||||
accessInfo,
|
||||
decodeEmoji = true,
|
||||
enlargeEmoji = DecodeOptions.emojiScaleReaction,
|
||||
enlargeCustomEmoji = DecodeOptions.emojiScaleReaction,
|
||||
emojiSizeMode = accessInfo.emojiSizeMode(),
|
||||
)
|
||||
val emojiSpan = reaction.toSpannableStringBuilder(options, status)
|
||||
confirm(R.string.reaction_remove_confirm, emojiSpan)
|
||||
}
|
||||
// 確認
|
||||
val options = DecodeOptions(
|
||||
activity,
|
||||
accessInfo,
|
||||
decodeEmoji = true,
|
||||
enlargeEmoji = DecodeOptions.emojiScaleReaction,
|
||||
enlargeCustomEmoji = DecodeOptions.emojiScaleReaction,
|
||||
emojiSizeMode = accessInfo.emojiSizeMode(),
|
||||
)
|
||||
val emojiSpan = reaction.toSpannableStringBuilder(options, status)
|
||||
confirm(R.string.reaction_remove_confirm, emojiSpan)
|
||||
// 削除
|
||||
var resultStatus: TootStatus? = null
|
||||
runApiTask(accessInfo) { client ->
|
||||
when {
|
||||
@ -236,7 +247,7 @@ fun ActMain.reactionRemove(
|
||||
}.toPostRequestBuilder()
|
||||
) // 成功すると204 no content
|
||||
|
||||
canMultipleReaction -> client.request(
|
||||
ti?.pleromaFeatures != null -> client.request(
|
||||
"/api/v1/pleroma/statuses/${status.id}/reactions/${reaction.name.encodePercent("@")}",
|
||||
"".toFormRequestBody().toDelete()
|
||||
)?.also { result ->
|
||||
@ -245,8 +256,8 @@ fun ActMain.reactionRemove(
|
||||
}
|
||||
|
||||
else -> client.request(
|
||||
"/api/v1/statuses/${status.id}/emoji_unreaction",
|
||||
"".toFormRequestBody().toPost()
|
||||
"/api/v1/statuses/${status.id}/emoji_reactions/${reaction.name.encodePercent("@")}",
|
||||
Request.Builder().delete(),
|
||||
)?.also { result ->
|
||||
// 成功すると新しいステータス
|
||||
resultStatus = TootParser(activity, accessInfo).status(result.jsonObject)
|
||||
@ -324,7 +335,7 @@ private fun ActMain.reactionWithoutUi(
|
||||
return
|
||||
}
|
||||
|
||||
val canMultipleReaction = InstanceCapability.canMultipleReaction(accessInfo)
|
||||
val ti = TootInstance.getCached(accessInfo)
|
||||
|
||||
val options = DecodeOptions(
|
||||
activity,
|
||||
@ -332,7 +343,7 @@ private fun ActMain.reactionWithoutUi(
|
||||
decodeEmoji = true,
|
||||
enlargeEmoji = DecodeOptions.emojiScaleReaction,
|
||||
enlargeCustomEmoji = DecodeOptions.emojiScaleReaction,
|
||||
emojiSizeMode = accessInfo.emojiSizeMode(),
|
||||
emojiSizeMode = accessInfo.emojiSizeMode(),
|
||||
)
|
||||
val emojiSpan = TootReaction.toSpannableStringBuilder(options, reactionCode, reactionImage)
|
||||
val isCustomEmoji = TootReaction.isCustomEmoji(reactionCode)
|
||||
@ -340,8 +351,8 @@ private fun ActMain.reactionWithoutUi(
|
||||
|
||||
launchAndShowError {
|
||||
when {
|
||||
isCustomEmoji && canMultipleReaction ->
|
||||
error("can't reaction with custom emoji from this account")
|
||||
isCustomEmoji && !InstanceCapability.canCustomEmojiReaction(accessInfo, ti) ->
|
||||
error("can't use custom emoji for reaction from this account.")
|
||||
|
||||
isCustomEmoji && url?.likePleromaStatusUrl() == true -> confirm(
|
||||
R.string.confirm_reaction_to_pleroma,
|
||||
@ -374,7 +385,7 @@ private fun ActMain.reactionWithoutUi(
|
||||
}.toPostRequestBuilder()
|
||||
) // 成功すると204 no content
|
||||
|
||||
canMultipleReaction -> client.request(
|
||||
ti?.pleromaFeatures != null -> client.request(
|
||||
"/api/v1/pleroma/statuses/${resolvedStatus.id}/reactions/${
|
||||
reactionCode.encodePercent("@")
|
||||
}",
|
||||
@ -510,18 +521,29 @@ fun ActMain.reactionFromAnotherAccount(
|
||||
}
|
||||
|
||||
fun ActMain.clickReaction(accessInfo: SavedAccount, column: Column, status: TootStatus) {
|
||||
val canMultipleReaction = InstanceCapability.canMultipleReaction(accessInfo)
|
||||
val hasMyReaction = status.reactionSet?.hasMyReaction() == true
|
||||
val bRemoveButton = hasMyReaction && !canMultipleReaction
|
||||
when {
|
||||
!TootReaction.canReaction(accessInfo) ->
|
||||
reactionFromAnotherAccount(
|
||||
accessInfo,
|
||||
status
|
||||
)
|
||||
bRemoveButton ->
|
||||
reactionRemove(column, status)
|
||||
else ->
|
||||
reactionAdd(column, status)
|
||||
val activity = this
|
||||
launchMain {
|
||||
try {
|
||||
val myReactionCount = status.reactionSet?.myReactionCount ?: 0
|
||||
val maxReactionPerAccount = InstanceCapability.maxReactionPerAccount(accessInfo)
|
||||
when {
|
||||
!TootReaction.canReaction(accessInfo) ->
|
||||
reactionFromAnotherAccount(
|
||||
accessInfo,
|
||||
status
|
||||
)
|
||||
|
||||
myReactionCount >= maxReactionPerAccount ->
|
||||
activity.showToast(
|
||||
true,
|
||||
getString(R.string.exceed_reaction_per_account, maxReactionPerAccount)
|
||||
)
|
||||
|
||||
else ->
|
||||
reactionAdd(column, status)
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
showToast(ex, "clickReaction failed.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,11 @@ import jp.juggler.subwaytooter.util.LinkHelper
|
||||
import jp.juggler.subwaytooter.util.VersionString
|
||||
import jp.juggler.util.coroutine.AppDispatchers.withTimeoutSafe
|
||||
import jp.juggler.util.coroutine.launchDefault
|
||||
import jp.juggler.util.data.*
|
||||
import jp.juggler.util.data.JsonObject
|
||||
import jp.juggler.util.data.asciiPattern
|
||||
import jp.juggler.util.data.buildJsonObject
|
||||
import jp.juggler.util.data.groupEx
|
||||
import jp.juggler.util.data.notEmpty
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import jp.juggler.util.log.withCaption
|
||||
import jp.juggler.util.network.toPostRequestBuilder
|
||||
@ -55,15 +59,6 @@ object InstanceCapability {
|
||||
fun visibilityLimited(ti: TootInstance?) =
|
||||
ti?.fedibirdCapabilities?.contains("visibility_limited") == true
|
||||
|
||||
fun emojiReaction(ai: SavedAccount, ti: TootInstance?) =
|
||||
when {
|
||||
ai.isPseudo -> false
|
||||
ai.isMisskey -> true
|
||||
else ->
|
||||
ti?.fedibirdCapabilities?.contains("emoji_reaction") == true ||
|
||||
ti?.pleromaFeatures?.contains("pleroma_emoji_reactions") == true
|
||||
}
|
||||
|
||||
fun statusReference(ai: SavedAccount, ti: TootInstance?) =
|
||||
when {
|
||||
ai.isPseudo -> false
|
||||
@ -79,19 +74,55 @@ object InstanceCapability {
|
||||
else -> ti?.fedibirdCapabilities != null && ti.versionGE(TootInstance.VERSION_2_7_0_rc1)
|
||||
}
|
||||
|
||||
fun canMultipleReaction(ai: SavedAccount, ti: TootInstance? = TootInstance.getCached(ai)) =
|
||||
fun canReaction(ai: SavedAccount, ti: TootInstance? = TootInstance.getCached(ai)) =
|
||||
when {
|
||||
ai.isPseudo -> false
|
||||
ai.isMisskey -> false
|
||||
else -> ti?.pleromaFeatures?.contains("pleroma_emoji_reactions") == true
|
||||
ai.isMisskey -> true
|
||||
ti?.fedibirdCapabilities?.contains("emoji_reaction") == true -> true
|
||||
ti?.pleromaFeatures?.contains("pleroma_emoji_reactions") == true -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
fun canCustomEmojiReaction(ai: SavedAccount, ti: TootInstance? = TootInstance.getCached(ai)) =
|
||||
when {
|
||||
ai.isPseudo -> false
|
||||
ai.isMisskey -> true
|
||||
ti?.fedibirdCapabilities?.contains("emoji_reaction") == true -> true
|
||||
ti?.pleromaFeatures?.contains("custom_emoji_reactions") == true -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
fun maxReactionPerAccount(
|
||||
ai: SavedAccount,
|
||||
ti: TootInstance? = TootInstance.getCached(ai),
|
||||
): Int =
|
||||
when {
|
||||
!canReaction(ai, ti) -> 0
|
||||
ai.isMisskey -> 1
|
||||
ti?.pleromaFeatures?.contains("pleroma_emoji_reactions") == true -> Int.MAX_VALUE - 10
|
||||
else ->
|
||||
ti?.configuration?.jsonObject("emoji_reactions")
|
||||
?.int("max_reactions_per_account")
|
||||
?: ti?.configuration?.int("emoji_reactions_per_account")
|
||||
?: 1
|
||||
}
|
||||
|
||||
// fun canMultipleReaction(ai: SavedAccount, ti: TootInstance? = TootInstance.getCached(ai)) =
|
||||
// when {
|
||||
// ai.isPseudo -> false
|
||||
// ti?.pleromaFeatures?.contains("pleroma_emoji_reactions") == true -> true
|
||||
// (ti?.configuration?.int("emoji_reactions_per_account") ?: 1) > 1 -> true
|
||||
// ai.isMisskey -> false
|
||||
// else -> false
|
||||
// }
|
||||
|
||||
fun listMyReactions(ai: SavedAccount, ti: TootInstance?) =
|
||||
when {
|
||||
ai.isPseudo -> false
|
||||
ai.isMisskey ->
|
||||
// m544 extension
|
||||
ti?.misskeyEndpoints?.contains("i/reactions") == true
|
||||
|
||||
else ->
|
||||
// fedibird extension
|
||||
ti?.fedibirdCapabilities?.contains("emoji_reaction") == true
|
||||
@ -402,7 +433,8 @@ class TootInstance(parser: TootParser, src: JsonObject) {
|
||||
*/
|
||||
private val reOldMisskeyCompatible = """\A[\d.]+:compatible:misskey:""".toRegex()
|
||||
|
||||
private val reBothCompatible = """\b(?:misskey|calckey)\b""".toRegex(RegexOption.IGNORE_CASE)
|
||||
private val reBothCompatible =
|
||||
"""\b(?:misskey|calckey)\b""".toRegex(RegexOption.IGNORE_CASE)
|
||||
|
||||
// 疑似アカウントの追加時に、インスタンスの検証を行う
|
||||
private suspend fun TootApiClient.getInstanceInformation(
|
||||
@ -496,6 +528,7 @@ class TootInstance(parser: TootParser, src: JsonObject) {
|
||||
!PrefB.bpEnablePixelfed.value &&
|
||||
!req.allowPixelfed ->
|
||||
tiError("currently Pixelfed instance is not supported.")
|
||||
|
||||
else -> qrr
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
|
@ -9,8 +9,13 @@ import jp.juggler.subwaytooter.span.NetworkEmojiSpan
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.util.DecodeOptions
|
||||
import jp.juggler.subwaytooter.util.EmojiDecoder
|
||||
import jp.juggler.util.data.*
|
||||
import java.util.*
|
||||
import jp.juggler.util.data.JsonArray
|
||||
import jp.juggler.util.data.JsonObject
|
||||
import jp.juggler.util.data.buildJsonObject
|
||||
import jp.juggler.util.data.decodeJsonArray
|
||||
import jp.juggler.util.data.notEmpty
|
||||
import jp.juggler.util.data.notZero
|
||||
import java.util.LinkedList
|
||||
|
||||
class TootReaction(
|
||||
// (fedibird絵文字リアクション) unicode絵文字はunicodeそのまま、 カスタム絵文字はコロンなしのshortcode
|
||||
@ -122,7 +127,7 @@ class TootReaction(
|
||||
fun canReaction(
|
||||
accessInfo: SavedAccount,
|
||||
ti: TootInstance? = TootInstance.getCached(accessInfo),
|
||||
) = InstanceCapability.emojiReaction(accessInfo, ti)
|
||||
) = InstanceCapability.canReaction(accessInfo, ti)
|
||||
|
||||
fun decodeEmojiQuery(jsonText: String?): List<TootReaction> =
|
||||
jsonText.notEmpty()?.let { src ->
|
||||
@ -256,23 +261,21 @@ class TootReaction(
|
||||
putNotNull("url", url)
|
||||
putNotNull("static_url", staticUrl)
|
||||
}
|
||||
|
||||
val isMyReaction get() = me
|
||||
}
|
||||
|
||||
class TootReactionSet(val isMisskey: Boolean) : LinkedList<TootReaction>() {
|
||||
|
||||
fun isMyReaction(reaction: TootReaction?): Boolean {
|
||||
return reaction?.me == true
|
||||
}
|
||||
|
||||
fun hasReaction() = any { it.count > 0 }
|
||||
|
||||
fun hasMyReaction() = any { it.count > 0 && isMyReaction(it) }
|
||||
val myReactionCount get() = count { it.count > 0 && it.me }
|
||||
|
||||
private fun getRaw(name: String?): TootReaction? =
|
||||
find { it.name == name }
|
||||
|
||||
operator fun get(name: String?): TootReaction? = when {
|
||||
name == null || name.isEmpty() -> null
|
||||
name.isNullOrEmpty() -> null
|
||||
isMisskey -> getRaw(name) ?: getRaw(TootReaction.getAnotherExpression(name))
|
||||
else -> getRaw(name)
|
||||
}
|
||||
|
@ -400,7 +400,7 @@ class TootStatus(
|
||||
return list
|
||||
}
|
||||
|
||||
fun updateReactionMastodon(newReactionSet: TootReactionSet) {
|
||||
fun updateReactionMastodon(newReactionSet: TootReactionSet?) {
|
||||
synchronized(this) {
|
||||
this.reactionSet = newReactionSet
|
||||
}
|
||||
|
@ -351,7 +351,7 @@ private fun Column.scanStatusById(
|
||||
// ストリーミングイベント受信時、該当アカウントのカラム全て対して呼ばれる
|
||||
fun Column.updateEmojiReactionByApiResponse(newStatus: TootStatus?) {
|
||||
newStatus ?: return
|
||||
val newReactionSet = newStatus.reactionSet ?: TootReactionSet(isMisskey = false)
|
||||
val newReactionSet = newStatus.reactionSet // Reaction削除の場合、nullは正常ケース
|
||||
scanStatusById("updateEmojiReactionByApiResponse", newStatus.id) { s ->
|
||||
s.updateReactionMastodon(newReactionSet)
|
||||
true
|
||||
|
@ -9,6 +9,7 @@ import androidx.core.content.ContextCompat
|
||||
import com.google.android.flexbox.FlexWrap
|
||||
import com.google.android.flexbox.FlexboxLayout
|
||||
import com.google.android.flexbox.JustifyContent
|
||||
import jp.juggler.subwaytooter.ActMain
|
||||
import jp.juggler.subwaytooter.ActMain.Companion.boostButtonSize
|
||||
import jp.juggler.subwaytooter.R
|
||||
import jp.juggler.subwaytooter.action.reactionAdd
|
||||
@ -16,9 +17,17 @@ import jp.juggler.subwaytooter.action.reactionFromAnotherAccount
|
||||
import jp.juggler.subwaytooter.action.reactionRemove
|
||||
import jp.juggler.subwaytooter.api.entity.TootReaction
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus
|
||||
import jp.juggler.subwaytooter.dialog.actionsDialog
|
||||
import jp.juggler.subwaytooter.pref.PrefB
|
||||
import jp.juggler.subwaytooter.pref.PrefI
|
||||
import jp.juggler.subwaytooter.util.*
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.util.DecodeOptions
|
||||
import jp.juggler.subwaytooter.util.NetworkEmojiInvalidator
|
||||
import jp.juggler.subwaytooter.util.copyToClipboard
|
||||
import jp.juggler.subwaytooter.util.emojiSizeMode
|
||||
import jp.juggler.subwaytooter.util.minWidthCompat
|
||||
import jp.juggler.subwaytooter.util.startMargin
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.data.notZero
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import jp.juggler.util.ui.attrColor
|
||||
@ -89,7 +98,7 @@ fun ItemViewHolder.makeReactionsView(status: TootStatus) {
|
||||
gravity = Gravity.CENTER
|
||||
minWidthCompat = textHeight.round()
|
||||
|
||||
background = if (reactionSet.isMyReaction(reaction)) {
|
||||
background = if (reaction.me) {
|
||||
// 自分がリアクションしたやつは背景を変える
|
||||
getAdaptiveRippleDrawableRound(
|
||||
act,
|
||||
@ -112,19 +121,15 @@ fun ItemViewHolder.makeReactionsView(status: TootStatus) {
|
||||
tag = reaction
|
||||
setOnClickListener {
|
||||
val taggedReaction = it.tag as? TootReaction
|
||||
if (status.reactionSet?.isMyReaction(taggedReaction) == true) {
|
||||
if (taggedReaction?.me == true) {
|
||||
act.reactionRemove(column, status, taggedReaction)
|
||||
} else {
|
||||
act.reactionAdd(column, status, taggedReaction?.name, taggedReaction?.staticUrl)
|
||||
}
|
||||
}
|
||||
setOnLongClickListener {
|
||||
val taggedReaction = it.tag as? TootReaction
|
||||
act.reactionFromAnotherAccount(
|
||||
accessInfo,
|
||||
statusShowing,
|
||||
taggedReaction
|
||||
)
|
||||
setOnLongClickListener { v ->
|
||||
(v.tag as? TootReaction)
|
||||
?.let { act.reactionLongClick(accessInfo, statusShowing, it) }
|
||||
true
|
||||
}
|
||||
// カスタム絵文字の場合、アニメーション等のコールバックを処理する必要がある
|
||||
@ -143,3 +148,23 @@ fun ItemViewHolder.makeReactionsView(status: TootStatus) {
|
||||
|
||||
llExtra.addView(box)
|
||||
}
|
||||
|
||||
fun ActMain.reactionLongClick(
|
||||
accessInfo: SavedAccount,
|
||||
statusShowing: TootStatus?,
|
||||
reaction: TootReaction?,
|
||||
) = launchAndShowError {
|
||||
reaction ?: return@launchAndShowError
|
||||
actionsDialog(getString(R.string.reaction) + " " + reaction.name) {
|
||||
action(getString(R.string.reaction_from_another_account)) {
|
||||
reactionFromAnotherAccount(
|
||||
accessInfo,
|
||||
statusShowing,
|
||||
reaction
|
||||
)
|
||||
}
|
||||
action(getString(R.string.copy_reaction_name)) {
|
||||
reaction.name.copyToClipboard(this@reactionLongClick)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,9 +13,29 @@ import com.google.android.flexbox.FlexboxLayout
|
||||
import com.google.android.flexbox.JustifyContent
|
||||
import jp.juggler.subwaytooter.ActMain
|
||||
import jp.juggler.subwaytooter.R
|
||||
import jp.juggler.subwaytooter.action.*
|
||||
import jp.juggler.subwaytooter.action.bookmarkFromAnotherAccount
|
||||
import jp.juggler.subwaytooter.action.boostFromAnotherAccount
|
||||
import jp.juggler.subwaytooter.action.clickBookmark
|
||||
import jp.juggler.subwaytooter.action.clickBoost
|
||||
import jp.juggler.subwaytooter.action.clickConversation
|
||||
import jp.juggler.subwaytooter.action.clickFavourite
|
||||
import jp.juggler.subwaytooter.action.clickFollow
|
||||
import jp.juggler.subwaytooter.action.clickQuote
|
||||
import jp.juggler.subwaytooter.action.clickReaction
|
||||
import jp.juggler.subwaytooter.action.clickReply
|
||||
import jp.juggler.subwaytooter.action.conversationOtherInstance
|
||||
import jp.juggler.subwaytooter.action.favouriteFromAnotherAccount
|
||||
import jp.juggler.subwaytooter.action.followFromAnotherAccount
|
||||
import jp.juggler.subwaytooter.action.quoteFromAnotherAccount
|
||||
import jp.juggler.subwaytooter.action.reactionFromAnotherAccount
|
||||
import jp.juggler.subwaytooter.action.replyFromAnotherAccount
|
||||
import jp.juggler.subwaytooter.actmain.nextPosition
|
||||
import jp.juggler.subwaytooter.api.entity.*
|
||||
import jp.juggler.subwaytooter.api.entity.InstanceCapability
|
||||
import jp.juggler.subwaytooter.api.entity.TootInstance
|
||||
import jp.juggler.subwaytooter.api.entity.TootNotification
|
||||
import jp.juggler.subwaytooter.api.entity.TootReaction
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus
|
||||
import jp.juggler.subwaytooter.api.entity.TootVisibility
|
||||
import jp.juggler.subwaytooter.column.Column
|
||||
import jp.juggler.subwaytooter.column.getContentColor
|
||||
import jp.juggler.subwaytooter.pref.PrefB
|
||||
@ -30,9 +50,20 @@ import jp.juggler.subwaytooter.util.CustomShareTarget
|
||||
import jp.juggler.subwaytooter.util.startMargin
|
||||
import jp.juggler.util.data.notZero
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import jp.juggler.util.ui.*
|
||||
import org.jetbrains.anko.*
|
||||
import jp.juggler.util.ui.applyAlphaMultiplier
|
||||
import jp.juggler.util.ui.attrColor
|
||||
import jp.juggler.util.ui.createColoredDrawable
|
||||
import jp.juggler.util.ui.setIconDrawableId
|
||||
import jp.juggler.util.ui.vg
|
||||
import org.jetbrains.anko.UI
|
||||
import org.jetbrains.anko.custom.customView
|
||||
import org.jetbrains.anko.dip
|
||||
import org.jetbrains.anko.frameLayout
|
||||
import org.jetbrains.anko.imageButton
|
||||
import org.jetbrains.anko.imageResource
|
||||
import org.jetbrains.anko.imageView
|
||||
import org.jetbrains.anko.matchParent
|
||||
import org.jetbrains.anko.wrapContent
|
||||
|
||||
enum class AdditionalButtonsPosition(
|
||||
val idx: Int, // spinner index start from 0
|
||||
@ -177,6 +208,7 @@ class StatusButtons(
|
||||
repliesCount == 1L -> "1"
|
||||
else -> ""
|
||||
}
|
||||
|
||||
PrefI.RC_ACTUAL -> repliesCount.toString()
|
||||
else -> ""
|
||||
}
|
||||
@ -216,6 +248,7 @@ class StatusButtons(
|
||||
status.reblogged ->
|
||||
PrefI.ipButtonBoostedColor.value.notZero()
|
||||
?: activity.attrColor(R.attr.colorButtonAccentBoost)
|
||||
|
||||
else ->
|
||||
colorTextContent
|
||||
},
|
||||
@ -228,6 +261,7 @@ class StatusButtons(
|
||||
boostsCount == 1L -> "1"
|
||||
else -> ""
|
||||
}
|
||||
|
||||
PrefI.RC_ACTUAL -> boostsCount.toString()
|
||||
else -> ""
|
||||
}
|
||||
@ -251,16 +285,26 @@ class StatusButtons(
|
||||
|
||||
private fun bindReactionButton(status: TootStatus) {
|
||||
btnReaction.vg(TootReaction.canReaction(accessInfo, ti))?.let {
|
||||
val canMultipleReaction = InstanceCapability.canMultipleReaction(accessInfo, ti)
|
||||
val hasMyReaction = status.reactionSet?.hasMyReaction() == true
|
||||
val bRemoveButton = hasMyReaction && !canMultipleReaction
|
||||
val myReactionCount: Int = status.reactionSet?.myReactionCount ?: 0
|
||||
val maxReactionPerAccount: Int =
|
||||
InstanceCapability.maxReactionPerAccount(accessInfo, ti)
|
||||
setButton(
|
||||
it,
|
||||
true,
|
||||
colorTextContent,
|
||||
if (bRemoveButton) R.drawable.ic_remove else R.drawable.ic_add,
|
||||
when (myReactionCount) {
|
||||
0 -> colorTextContent
|
||||
else -> PrefI.ipButtonReactionedColor.value.notZero()
|
||||
?: activity.attrColor(R.attr.colorButtonAccentReaction)
|
||||
},
|
||||
when (myReactionCount >= maxReactionPerAccount) {
|
||||
true -> R.drawable.outline_face_retouching_off
|
||||
else -> R.drawable.outline_face
|
||||
},
|
||||
activity.getString(
|
||||
if (bRemoveButton) R.string.reaction_remove else R.string.reaction_add
|
||||
when (myReactionCount >= maxReactionPerAccount) {
|
||||
true -> R.string.reaction_remove
|
||||
else -> R.string.reaction_add
|
||||
},
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -286,6 +330,7 @@ class StatusButtons(
|
||||
status.favourited ->
|
||||
PrefI.ipButtonFavoritedColor.value.notZero()
|
||||
?: activity.attrColor(R.attr.colorButtonAccentFavourite)
|
||||
|
||||
else -> colorTextContent
|
||||
},
|
||||
when {
|
||||
@ -300,6 +345,7 @@ class StatusButtons(
|
||||
favouritesCount == 1L -> "1"
|
||||
else -> ""
|
||||
}
|
||||
|
||||
PrefI.RC_ACTUAL -> favouritesCount.toString()
|
||||
else -> ""
|
||||
}
|
||||
@ -330,6 +376,7 @@ class StatusButtons(
|
||||
status.bookmarked ->
|
||||
PrefI.ipButtonBookmarkedColor.value.notZero()
|
||||
?: activity.attrColor(R.attr.colorButtonAccentBookmark)
|
||||
|
||||
else ->
|
||||
colorTextContent
|
||||
},
|
||||
@ -536,6 +583,7 @@ class StatusButtons(
|
||||
itemViewHolder.listAdapter,
|
||||
status = status
|
||||
)
|
||||
|
||||
btnReply -> clickReply(accessInfo, status)
|
||||
btnQuote -> clickQuote(accessInfo, status)
|
||||
btnBoost -> clickBoost(accessInfo, status, willToast = bSimpleList)
|
||||
@ -836,6 +884,7 @@ class StatusButtonsViewHolder(
|
||||
additionalButtons()
|
||||
normalButtons()
|
||||
}
|
||||
|
||||
else -> {
|
||||
normalButtons()
|
||||
additionalButtons()
|
||||
|
@ -9,7 +9,11 @@ import jp.juggler.subwaytooter.api.ApiError
|
||||
import jp.juggler.subwaytooter.api.TootApiCallback
|
||||
import jp.juggler.subwaytooter.api.TootApiClient
|
||||
import jp.juggler.subwaytooter.api.auth.AuthMastodon
|
||||
import jp.juggler.subwaytooter.api.entity.*
|
||||
import jp.juggler.subwaytooter.api.entity.InstanceCapability
|
||||
import jp.juggler.subwaytooter.api.entity.TootInstance
|
||||
import jp.juggler.subwaytooter.api.entity.TootNotification
|
||||
import jp.juggler.subwaytooter.api.entity.TootPushSubscription
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus
|
||||
import jp.juggler.subwaytooter.api.push.ApiPushMastodon
|
||||
import jp.juggler.subwaytooter.pref.PrefDevice
|
||||
import jp.juggler.subwaytooter.pref.lazyContext
|
||||
@ -18,7 +22,13 @@ import jp.juggler.subwaytooter.table.AccountNotificationStatus
|
||||
import jp.juggler.subwaytooter.table.PushMessage
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.appDatabase
|
||||
import jp.juggler.util.data.*
|
||||
import jp.juggler.util.data.JsonObject
|
||||
import jp.juggler.util.data.decodeBase64
|
||||
import jp.juggler.util.data.digestSHA256Base64Url
|
||||
import jp.juggler.util.data.ellipsizeDot3
|
||||
import jp.juggler.util.data.encodeBase64Url
|
||||
import jp.juggler.util.data.notBlank
|
||||
import jp.juggler.util.data.notEmpty
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import jp.juggler.util.time.parseTimeIso8601
|
||||
import kotlinx.coroutines.isActive
|
||||
@ -57,6 +67,7 @@ class PushMastodon(
|
||||
subLog.i(msg)
|
||||
null
|
||||
}
|
||||
|
||||
else -> lazyContext.getString(
|
||||
R.string.push_subscription_app_server_hash_missing_error
|
||||
)
|
||||
@ -106,6 +117,7 @@ class PushMastodon(
|
||||
null -> {
|
||||
subLog.i(R.string.push_subscription_is_not_required)
|
||||
}
|
||||
|
||||
else -> {
|
||||
subLog.i(R.string.push_subscription_delete_current)
|
||||
api.deletePushSubscription(account)
|
||||
@ -261,9 +273,9 @@ class PushMastodon(
|
||||
// fedibird拡張
|
||||
// https://github.com/fedibird/mastodon/blob/fedibird/app/controllers/api/v1/push/subscriptions_controller.rb#L55
|
||||
// https://github.com/fedibird/mastodon/blob/fedibird/app/models/notification.rb
|
||||
if (!ti.pleromaFeatures.isNullOrEmpty()) {
|
||||
if (ti.pleromaFeatures?.contains("pleroma_emoji_reactions") == true) {
|
||||
dst[TootNotification.TYPE_EMOJI_REACTION_PLEROMA] = notificationReaction
|
||||
} else if (!ti.fedibirdCapabilities.isNullOrEmpty()) {
|
||||
} else if (ti.fedibirdCapabilities?.contains("emoji_reaction") == true) {
|
||||
dst[TootNotification.TYPE_EMOJI_REACTION] = notificationReaction
|
||||
}
|
||||
dst[TootNotification.TYPE_SCHEDULED_STATUS] = notificationPost // 設定項目不足
|
||||
@ -293,11 +305,11 @@ class PushMastodon(
|
||||
// Fedibird拡張
|
||||
|
||||
TootNotification.TYPE_EMOJI_REACTION,
|
||||
-> InstanceCapability.emojiReaction(account, ti)
|
||||
-> InstanceCapability.canReaction(account, ti)
|
||||
|
||||
// pleromaの絵文字リアクションはalertに指定できない
|
||||
TootNotification.TYPE_EMOJI_REACTION_PLEROMA,
|
||||
-> InstanceCapability.emojiReaction(account, ti)
|
||||
-> InstanceCapability.canReaction(account, ti)
|
||||
|
||||
TootNotification.TYPE_SCHEDULED_STATUS,
|
||||
-> InstanceCapability.scheduledStatus(account, ti)
|
||||
@ -368,6 +380,7 @@ class PushMastodon(
|
||||
|
||||
pm.iconSmall = a.supplyBaseUrl(json.string("badge"))
|
||||
}
|
||||
|
||||
else -> {
|
||||
// Mastodon 4.0
|
||||
// {
|
||||
|
@ -4,6 +4,7 @@ import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.SystemClock
|
||||
import android.text.style.ReplacementSpan
|
||||
import androidx.annotation.IntRange
|
||||
import androidx.core.content.ContextCompat
|
||||
@ -16,6 +17,8 @@ import jp.juggler.subwaytooter.util.EmojiImageRect
|
||||
import jp.juggler.subwaytooter.util.EmojiSizeMode
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import java.lang.ref.WeakReference
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
class NetworkEmojiSpan constructor(
|
||||
private val url: String,
|
||||
@ -48,6 +51,7 @@ class NetworkEmojiSpan constructor(
|
||||
private val rectSrc = Rect()
|
||||
|
||||
private var lastMeasuredWidth = 0f
|
||||
private var lastRequestTime = 0L
|
||||
|
||||
private val emojiImageRect = EmojiImageRect(
|
||||
sizeMode = sizeMode,
|
||||
@ -156,22 +160,35 @@ class NetworkEmojiSpan constructor(
|
||||
return false
|
||||
}
|
||||
rectSrc.set(0, 0, srcWidth, srcHeight)
|
||||
val aspect = srcWidth.toFloat() / srcHeight.toFloat()
|
||||
emojiImageRect.updateRect(
|
||||
url = url,
|
||||
aspectArg = srcWidth.toFloat() / srcHeight.toFloat(),
|
||||
aspectArg = aspect,
|
||||
textPaint.textSize,
|
||||
baseline.toFloat()
|
||||
)
|
||||
val clipBounds = canvas.clipBounds
|
||||
val clipWidth = clipBounds.width()
|
||||
// 最後にgetSizeで返した幅と異なるか、現在のTextViewのClip幅より大きいなら
|
||||
// 再レイアウトを要求する
|
||||
if (emojiImageRect.emojiWidth != lastMeasuredWidth) {
|
||||
log.i("requestLayout by width changed")
|
||||
invalidateCallback.requestLayout()
|
||||
} else if (emojiImageRect.emojiWidth > clipWidth) {
|
||||
log.i("requestLayout by clipWidth ${emojiImageRect.emojiWidth}/${clipWidth}")
|
||||
invalidateCallback.requestLayout()
|
||||
val now = SystemClock.elapsedRealtime()
|
||||
if (now - lastRequestTime >= 1000L) {
|
||||
// 最後にgetSizeで返した幅と異なるか、現在のTextViewのClip幅より大きいなら
|
||||
// 再レイアウトを要求する
|
||||
|
||||
val willLayout = when {
|
||||
|
||||
!equalsEmojiWidth(emojiImageRect.emojiWidth, lastMeasuredWidth) -> true
|
||||
|
||||
emojiImageRect.emojiWidth > clipWidth -> {
|
||||
log.i("requestLayout by clipWidth ${emojiImageRect.emojiWidth}/${clipWidth}")
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
if (willLayout) {
|
||||
invalidateCallback.requestLayout()
|
||||
lastRequestTime = now
|
||||
}
|
||||
}
|
||||
|
||||
canvas.save()
|
||||
@ -201,6 +218,19 @@ class NetworkEmojiSpan constructor(
|
||||
return true
|
||||
}
|
||||
|
||||
// getSizeで使うinitialAspectはリサイズの影響を受けないため、誤差が出る
|
||||
// 数%の誤差を許容するような比較を行う
|
||||
private fun equalsEmojiWidth(a: Float, b: Float): Boolean {
|
||||
if (a == b) return true
|
||||
val max = max(a, b)
|
||||
val min = min(a, b)
|
||||
if (min < 1f) return false
|
||||
val scale = max / min
|
||||
if (scale in 0.95f..1.05f) return true
|
||||
log.i("equalsEmojiWidth: a=$a b=$b scale=$scale")
|
||||
return false
|
||||
}
|
||||
|
||||
private fun handleFrameLoaded(frames: ApngFrames?) {
|
||||
frames?.aspect?.let {
|
||||
invalidateCallback?.requestLayout()
|
||||
|
@ -130,7 +130,7 @@ suspend fun Context.accountListCanReaction(pickupHost: Host? = null) =
|
||||
if (ti == null) {
|
||||
ri?.error?.let { log.w(it) }
|
||||
false
|
||||
} else InstanceCapability.emojiReaction(a, ti)
|
||||
} else InstanceCapability.canReaction(a, ti)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
5
app/src/main/res/drawable/outline_face.xml
Normal file
5
app/src/main/res/drawable/outline_face.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M10.25,13c0,0.69 -0.56,1.25 -1.25,1.25S7.75,13.69 7.75,13s0.56,-1.25 1.25,-1.25 1.25,0.56 1.25,1.25zM15,11.75c-0.69,0 -1.25,0.56 -1.25,1.25s0.56,1.25 1.25,1.25 1.25,-0.56 1.25,-1.25 -0.56,-1.25 -1.25,-1.25zM22,12c0,5.52 -4.48,10 -10,10S2,17.52 2,12 6.48,2 12,2s10,4.48 10,10zM10.66,4.12C12.06,6.44 14.6,8 17.5,8c0.46,0 0.91,-0.05 1.34,-0.12C17.44,5.56 14.9,4 12,4c-0.46,0 -0.91,0.05 -1.34,0.12zM4.42,9.47c1.71,-0.97 3.03,-2.55 3.66,-4.44C6.37,6 5.05,7.58 4.42,9.47zM20,12c0,-0.78 -0.12,-1.53 -0.33,-2.24 -0.7,0.15 -1.42,0.24 -2.17,0.24 -3.13,0 -5.92,-1.44 -7.76,-3.69C8.69,8.87 6.6,10.88 4,11.86c0.01,0.04 0,0.09 0,0.14 0,4.41 3.59,8 8,8s8,-3.59 8,-8z"/>
|
||||
</vector>
|
@ -0,0 +1,7 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M9,13m-1.25,0a1.25,1.25 0,1 1,2.5 0a1.25,1.25 0,1 1,-2.5 0"/>
|
||||
<path android:fillColor="@android:color/white" android:pathData="M17.5,10c0.75,0 1.47,-0.09 2.17,-0.24C19.88,10.47 20,11.22 20,12c0,1.22 -0.28,2.37 -0.77,3.4l1.49,1.49C21.53,15.44 22,13.78 22,12c0,-5.52 -4.48,-10 -10,-10c-1.78,0 -3.44,0.47 -4.89,1.28l5.33,5.33C13.93,9.49 15.65,10 17.5,10zM10.66,4.12C11.09,4.05 11.54,4 12,4c2.9,0 5.44,1.56 6.84,3.88C18.41,7.95 17.96,8 17.5,8C14.6,8 12.06,6.44 10.66,4.12z"/>
|
||||
<path android:fillColor="@android:color/white" android:pathData="M1.89,3.72l2.19,2.19C2.78,7.6 2,9.71 2,12c0,5.52 4.48,10 10,10c2.29,0 4.4,-0.78 6.09,-2.08l2.19,2.19l1.41,-1.41L3.31,2.31L1.89,3.72zM16.66,18.49C15.35,19.44 13.74,20 12,20c-4.41,0 -8,-3.59 -8,-8c0,-0.05 0.01,-0.1 0,-0.14c1.39,-0.52 2.63,-1.35 3.64,-2.39L16.66,18.49zM6.23,8.06C5.7,8.61 5.09,9.09 4.42,9.47C4.68,8.7 5.05,7.99 5.51,7.34L6.23,8.06z"/>
|
||||
</vector>
|
@ -1274,4 +1274,6 @@
|
||||
<string name="autocomplete_list_loading">入力補完リストの読み込み中…</string>
|
||||
<string name="media_count">%1$d個の添付データ (タップで全て表示)</string>
|
||||
<string name="applied_when_post">投稿送信時に反映されます</string>
|
||||
<string name="exceed_reaction_per_account">リアクション個数の制限(%1$d)</string>
|
||||
<string name="copy_reaction_name">Copy reaction name</string>
|
||||
</resources>
|
||||
|
@ -1282,4 +1282,6 @@
|
||||
<string name="autocomplete_list_loading">loading autocomplete list…</string>
|
||||
<string name="media_count">%1$d attachments (tap to see all)</string>
|
||||
<string name="applied_when_post">applied when post.</string>
|
||||
<string name="exceed_reaction_per_account">Reaction limit is %1$d.</string>
|
||||
<string name="copy_reaction_name">Copy reaction name</string>
|
||||
</resources>
|
||||
|
Loading…
x
Reference in New Issue
Block a user