リアクションカラムに絵文字による絞り込み機能を追加

This commit is contained in:
tateisu 2021-05-21 17:20:14 +09:00
parent 874411a536
commit be0ee77ade
15 changed files with 322 additions and 87 deletions

View File

@ -207,7 +207,10 @@ object ColumnEncoder {
dst[KEY_SEARCH_RESOLVE] = search_resolve
}
ColumnType.SEARCH_MSP, ColumnType.SEARCH_TS, ColumnType.SEARCH_NOTESTOCK -> {
ColumnType.REACTIONS,
ColumnType.SEARCH_MSP,
ColumnType.SEARCH_TS,
ColumnType.SEARCH_NOTESTOCK -> {
dst[KEY_SEARCH_QUERY] = search_query
}
@ -328,10 +331,14 @@ object ColumnEncoder {
search_resolve = src.optBoolean(KEY_SEARCH_RESOLVE, false)
}
ColumnType.SEARCH_MSP, ColumnType.SEARCH_TS, ColumnType.SEARCH_NOTESTOCK -> search_query =
src.optString(KEY_SEARCH_QUERY)
ColumnType.REACTIONS,
ColumnType.SEARCH_MSP,
ColumnType.SEARCH_TS,
ColumnType.SEARCH_NOTESTOCK ->
search_query = src.optString(KEY_SEARCH_QUERY)
ColumnType.INSTANCE_INFORMATION -> instance_uri = src.optString(KEY_INSTANCE_URI)
ColumnType.INSTANCE_INFORMATION ->
instance_uri = src.optString(KEY_INSTANCE_URI)
ColumnType.PROFILE_DIRECTORY -> {
instance_uri = src.optString(KEY_INSTANCE_URI)

View File

@ -705,6 +705,15 @@ fun Column.makeListTlUrl(): String {
}
}
fun Column.makeReactionsUrl(): String {
if (isMisskey) error("misskey has no api to list your reactions.")
val basePath = ApiPath.PATH_REACTIONS
val list = TootReaction.decodeEmojiQuery(search_query)
if (list.isEmpty()) return basePath
val delm = if (basePath.contains("?")) "&" else "?"
return "$basePath$delm${list.joinToString("&") { "emojis[]=${it.name.encodePercent()}" }}"
}
fun Column.makeAntennaTlUrl(): String {
return if (isMisskey) {
"/api/antennas/notes"

View File

@ -700,18 +700,18 @@ enum class ColumnType(
if (isMisskey) {
TootApiResult("misskey has no api to list your reactions")
} else {
getStatusList(client, ApiPath.PATH_REACTIONS)
getStatusList(client,column.makeReactionsUrl())
}
},
refresh = { client ->
getStatusList(client, ApiPath.PATH_REACTIONS)
getStatusList(client,column.makeReactionsUrl())
},
gap = { client ->
getStatusList(
client,
ApiPath.PATH_REACTIONS,
column.makeReactionsUrl(),
mastodonFilterByIdRange = false
)
},

View File

@ -14,6 +14,9 @@ import android.widget.*
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.flexbox.FlexWrap
import com.google.android.flexbox.FlexboxLayout
import com.google.android.flexbox.JustifyContent
import com.omadahealth.github.swipyrefreshlayout.library.SwipyRefreshLayout
import com.omadahealth.github.swipyrefreshlayout.library.SwipyRefreshLayoutDirection
import jp.juggler.subwaytooter.streaming.*
@ -84,7 +87,10 @@ class ColumnViewHolder(
lateinit var btnSearch: ImageButton
lateinit var btnSearchClear: ImageButton
lateinit var btnEmojiAdd: ImageButton
lateinit var etSearch: EditText
lateinit var flEmoji: FlexboxLayout
lateinit var tvEmojiDesc: TextView
lateinit var cbResolve: CheckBox
lateinit var etRegexFilter: EditText
lateinit var tvRegexFilterError: TextView
@ -168,6 +174,7 @@ class ColumnViewHolder(
var bRefreshErrorWillShown = false
val extra_invalidator_list = ArrayList<NetworkEmojiInvalidator>()
val emojiQueryInvalidatorList = ArrayList<NetworkEmojiInvalidator>()
val announcementContentInvalidator: NetworkEmojiInvalidator
@ -372,7 +379,6 @@ class ColumnViewHolder(
// animator.supportsChangeAnimations = false
// }
btnListAdd.setOnClickListener(this)
etListName.setOnEditorActionListener { _, actionId, _ ->
var handled = false
@ -383,54 +389,61 @@ class ColumnViewHolder(
handled
}
btnQuickFilterAll.setOnClickListener(this)
btnQuickFilterMention.setOnClickListener(this)
btnQuickFilterFavourite.setOnClickListener(this)
btnQuickFilterBoost.setOnClickListener(this)
btnQuickFilterFollow.setOnClickListener(this)
btnQuickFilterPost.setOnClickListener(this)
btnQuickFilterReaction.setOnClickListener(this)
btnQuickFilterVote.setOnClickListener(this)
llColumnHeader.setOnClickListener(this)
btnAnnouncements.setOnClickListener(this)
btnColumnSetting.setOnClickListener(this)
btnColumnReload.setOnClickListener(this)
btnColumnClose.setOnClickListener(this)
btnColumnClose.setOnLongClickListener(this)
btnDeleteNotification.setOnClickListener(this)
btnConfirmMail.setOnClickListener(this)
btnColor.setOnClickListener(this)
btnLanguageFilter.setOnClickListener(this)
refreshLayout.setOnRefreshListener(this)
refreshLayout.setDistanceToTriggerSync((0.5f + 20f * activity.density).toInt())
llRefreshError.setOnClickListener(this)
arrayOf(
btnAnnouncements,
btnAnnouncementsNext,
btnAnnouncementsPrev,
btnColor,
btnColumnClose,
btnColumnReload,
btnColumnSetting,
btnConfirmMail,
btnDeleteNotification,
btnEmojiAdd,
btnLanguageFilter,
btnListAdd,
btnQuickFilterAll,
btnQuickFilterBoost,
btnQuickFilterFavourite,
btnQuickFilterFollow,
btnQuickFilterMention,
btnQuickFilterPost,
btnQuickFilterReaction,
btnQuickFilterVote,
btnSearch,
btnSearchClear,
llColumnHeader,
llRefreshError,
btnAnnouncementsPrev.setOnClickListener(this)
btnAnnouncementsNext.setOnClickListener(this)
).forEach { it.setOnClickListener(this) }
cbDontCloseColumn.setOnCheckedChangeListener(this)
cbRemoteOnly.setOnCheckedChangeListener(this)
cbWithAttachment.setOnCheckedChangeListener(this)
cbWithHighlight.setOnCheckedChangeListener(this)
cbDontShowBoost.setOnCheckedChangeListener(this)
cbDontShowFollow.setOnCheckedChangeListener(this)
cbDontShowFavourite.setOnCheckedChangeListener(this)
cbDontShowReply.setOnCheckedChangeListener(this)
cbDontShowReaction.setOnCheckedChangeListener(this)
cbDontShowVote.setOnCheckedChangeListener(this)
cbDontShowNormalToot.setOnCheckedChangeListener(this)
cbDontShowNonPublicToot.setOnCheckedChangeListener(this)
cbInstanceLocal.setOnCheckedChangeListener(this)
cbDontStreaming.setOnCheckedChangeListener(this)
cbDontAutoRefresh.setOnCheckedChangeListener(this)
cbHideMediaDefault.setOnCheckedChangeListener(this)
cbSystemNotificationNotRelated.setOnCheckedChangeListener(this)
cbEnableSpeech.setOnCheckedChangeListener(this)
cbOldApi.setOnCheckedChangeListener(this)
btnColumnClose.setOnLongClickListener(this)
arrayOf(
cbDontAutoRefresh,
cbDontCloseColumn,
cbDontShowBoost,
cbDontShowFavourite,
cbDontShowFollow,
cbDontShowNonPublicToot,
cbDontShowNormalToot,
cbDontShowReaction,
cbDontShowReply,
cbDontShowVote,
cbDontStreaming,
cbEnableSpeech,
cbHideMediaDefault,
cbInstanceLocal,
cbOldApi,
cbRemoteOnly,
cbSystemNotificationNotRelated,
cbWithAttachment,
cbWithHighlight,
).forEach { it.setOnCheckedChangeListener(this) }
if (Pref.bpMoveNotificationsQuickFilter(activity.pref)) {
(svQuickFilter.parent as? ViewGroup)?.removeView(svQuickFilter)
@ -455,6 +468,7 @@ class ColumnViewHolder(
tvColumnContext.textSize = acctSize
tvColumnStatus.textSize = acctSize
tvColumnIndex.textSize = acctSize
tvEmojiDesc.textSize = acctSize
}
initLoadingTextView()
@ -482,8 +496,6 @@ class ColumnViewHolder(
btnColumnClose.layoutParams.height = wh
btnColumnClose.setPaddingRelative(pad, pad, pad, pad)
btnSearch.setOnClickListener(this)
btnSearchClear.setOnClickListener(this)
etSearch.setOnEditorActionListener(TextView.OnEditorActionListener { _, actionId, _ ->
if (!binding_busy) {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
@ -1076,6 +1088,7 @@ class ColumnViewHolder(
isBaselineAligned = false
gravity = Gravity.CENTER
etSearch = editText {
id = View.generateViewId()
imeOptions = EditorInfo.IME_ACTION_SEARCH
@ -1086,6 +1099,25 @@ class ColumnViewHolder(
weight = 1f
}
flEmoji = flexboxLayout {
flexWrap = FlexWrap.WRAP
justifyContent = JustifyContent.FLEX_START
}.lparams(0, wrapContent) {
weight = 1f
}
btnEmojiAdd = imageButton {
backgroundResource = R.drawable.btn_bg_transparent_round6dp
contentDescription = context.getString(R.string.add)
imageResource = R.drawable.ic_add
imageTintList = ColorStateList.valueOf(
context.attrColor(R.attr.colorVectorDrawable)
)
}.lparams(dip(40), dip(40)) {
startMargin = dip(4)
}
btnSearchClear = imageButton {
backgroundResource = R.drawable.btn_bg_transparent_round6dp
contentDescription = context.getString(R.string.clear)
@ -1113,6 +1145,12 @@ class ColumnViewHolder(
text = context.getString(R.string.resolve_non_local_account)
}.lparams(wrapContent, wrapContent) // チェックボックスの余白はタッチ判定外
tvEmojiDesc = textView {
text = context.getString(R.string.long_tap_to_delete)
textColor = context.attrColor(R.attr.colorColumnHeaderPageNumber)
textSize = 12f
}.lparams(wrapContent, wrapContent)
} // end of search bar
llListList = linearLayout {

View File

@ -6,10 +6,7 @@ import jp.juggler.subwaytooter.action.Action_Account
import jp.juggler.subwaytooter.action.Action_List
import jp.juggler.subwaytooter.action.Action_Notification
import jp.juggler.subwaytooter.api.entity.TootAnnouncement
import jp.juggler.util.hideKeyboard
import jp.juggler.util.isCheckedNoAnime
import jp.juggler.util.showToast
import jp.juggler.util.withCaption
import jp.juggler.util.*
import java.util.regex.Pattern
fun ColumnViewHolder.onListListUpdated() {
@ -193,23 +190,28 @@ fun ColumnViewHolder.onClickImpl(v: View?) {
etSearch.hideKeyboard()
etSearch.setText(column.search_query)
cbResolve.isCheckedNoAnime = column.search_resolve
}else if(column.type == ColumnType.REACTIONS){
updateReactionQueryView()
}
refreshLayout.isRefreshing = false
column.startLoading()
}
btnSearch -> {
etSearch.hideKeyboard()
column.search_query = etSearch.text.toString().trim { it <= ' ' }
column.search_resolve = cbResolve.isChecked
if( column.isSearchColumn){
etSearch.hideKeyboard()
column.search_query = etSearch.text.toString().trim { it <= ' ' }
column.search_resolve = cbResolve.isChecked
}
activity.app_state.saveColumnList()
column.startLoading()
}
btnSearchClear -> {
etSearch.setText("")
column.search_query = ""
column.search_resolve = cbResolve.isChecked
etSearch.setText("")
flEmoji.removeAllViews()
activity.app_state.saveColumnList()
column.startLoading()
}
@ -279,7 +281,10 @@ fun ColumnViewHolder.onClickImpl(v: View?) {
btnConfirmMail -> {
Action_Account.resendConfirmMail(activity, column.access_info)
}
btnEmojiAdd ->{
addEmojiQuery()
}
}
}

View File

@ -139,6 +139,16 @@ fun ColumnViewHolder.onPageCreate(column: Column, page_idx: Int, page_count: Int
for (invalidator in emojiQueryInvalidatorList) {
invalidator.register(null)
}
emojiQueryInvalidatorList.clear()
for (invalidator in extra_invalidator_list) {
invalidator.register(null)
}
extra_invalidator_list.clear()
cbDontCloseColumn.isCheckedNoAnime = column.dont_close
cbRemoteOnly.isCheckedNoAnime = column.remote_only
cbWithAttachment.isCheckedNoAnime = column.with_attachment
@ -193,12 +203,34 @@ fun ColumnViewHolder.onPageCreate(column: Column, page_idx: Int, page_count: Int
btnDeleteNotification.vg(column.isNotificationColumn)
llSearch.vg(column.isSearchColumn)?.let {
btnSearchClear.vg(Pref.bpShowSearchClear(activity.pref))
when {
column.isSearchColumn -> {
llSearch.vg(true)
flEmoji.vg(false)
tvEmojiDesc.vg(false)
btnEmojiAdd.vg(false)
etSearch.vg(true)
btnSearchClear.vg(Pref.bpShowSearchClear(activity.pref))
cbResolve.vg(column.type == ColumnType.SEARCH)
}
column.type == ColumnType.REACTIONS -> {
llSearch.vg(true)
flEmoji.vg(true)
tvEmojiDesc.vg(true)
btnEmojiAdd.vg(true)
etSearch.vg(false)
btnSearchClear.vg(false)
cbResolve.vg(false)
}
else -> llSearch.vg(false)
}
llListList.vg(column.type == ColumnType.LIST_LIST)
cbResolve.vg(column.type == ColumnType.SEARCH)
llHashtagExtra.vg(column.hasHashtagExtra)
etHashtagExtraAny.setText(column.hashtag_any)

View File

@ -0,0 +1,112 @@
package jp.juggler.subwaytooter
import android.widget.Button
import androidx.core.content.ContextCompat
import com.google.android.flexbox.FlexboxLayout
import jp.juggler.subwaytooter.api.entity.TootReaction
import jp.juggler.subwaytooter.dialog.EmojiPicker
import jp.juggler.subwaytooter.emoji.CustomEmoji
import jp.juggler.subwaytooter.emoji.UnicodeEmoji
import jp.juggler.subwaytooter.util.DecodeOptions
import jp.juggler.subwaytooter.util.NetworkEmojiInvalidator
import jp.juggler.subwaytooter.util.minWidthCompat
import jp.juggler.subwaytooter.util.startMargin
import org.jetbrains.anko.allCaps
fun ColumnViewHolder.addEmojiQuery(reaction:TootReaction? =null){
val column = this.column?:return
if(reaction==null){
EmojiPicker(activity, column.access_info, closeOnSelected = true) { result ->
val newReaction = when (val emoji = result.emoji) {
is UnicodeEmoji -> TootReaction(name = emoji.unifiedCode)
is CustomEmoji -> TootReaction(
name=emoji.shortcode,
url = emoji.url,
static_url = emoji.static_url
)
}
addEmojiQuery(newReaction)
}.show()
return
}
val list = TootReaction.decodeEmojiQuery(column.search_query).toMutableList()
list.add(reaction)
column.search_query = TootReaction. encodeEmojiQuery(list)
updateReactionQueryView()
activity.app_state.saveColumnList()
}
private fun ColumnViewHolder.removeEmojiQuery(target:TootReaction?){
target ?: return
val list = TootReaction.decodeEmojiQuery(column?.search_query).filter { it.name != target.name }
column?.search_query = TootReaction.encodeEmojiQuery(list)
updateReactionQueryView()
activity.app_state.saveColumnList()
}
fun ColumnViewHolder.updateReactionQueryView() {
val column = this.column ?: return
flEmoji.removeAllViews()
for (invalidator in emojiQueryInvalidatorList) {
invalidator.register(null)
}
emojiQueryInvalidatorList.clear()
val options = DecodeOptions(
activity,
column.access_info,
decodeEmoji = true,
enlargeEmoji = 1.5f,
enlargeCustomEmoji = 1.5f
)
val act = this.activity // not Button(View).getActivity()
val buttonHeight = ActMain.boostButtonSize
val marginBetween = (buttonHeight.toFloat() * 0.05f + 0.5f).toInt()
val paddingH = (buttonHeight.toFloat() * 0.1f + 0.5f).toInt()
val paddingV = (buttonHeight.toFloat() * 0.1f + 0.5f).toInt()
val contentColor = column.getContentColor()
TootReaction.decodeEmojiQuery(column.search_query).forEachIndexed { index, reaction ->
val ssb = reaction.toSpannableStringBuilder(options, status=null)
val b = Button(activity).apply {
layoutParams = FlexboxLayout.LayoutParams(
FlexboxLayout.LayoutParams.WRAP_CONTENT,
buttonHeight
).apply {
if(index >0 ) startMargin = marginBetween
}
minWidthCompat = buttonHeight
background = ContextCompat.getDrawable(act,R.drawable.btn_bg_transparent_round6dp)
setTextColor(contentColor)
setPadding(paddingH, paddingV, paddingH, paddingV)
text = ssb
allCaps = false
tag = reaction
setOnLongClickListener {
removeEmojiQuery(it.tag as? TootReaction)
true
}
// カスタム絵文字の場合、アニメーション等のコールバックを処理する必要がある
val invalidator = NetworkEmojiInvalidator(act.handler, this)
invalidator.register(ssb)
emojiQueryInvalidatorList.add(invalidator)
}
flEmoji.addView(b)
}
}

View File

@ -124,8 +124,6 @@ fun ItemViewHolder.makeReactionsView(status: TootStatus) {
enlargeCustomEmoji = 1.5f
)
var lastButton: View? = null
reactionSet?.forEachIndexed { index, reaction ->
val ssb = reaction.toSpannableStringBuilder(options, status)
.also { it.append(" ${reaction.count}") }
@ -186,13 +184,8 @@ fun ItemViewHolder.makeReactionsView(status: TootStatus) {
extra_invalidator_list.add(invalidator)
}
box.addView(b)
lastButton = b
}
lastButton
?.layoutParams
?.cast<ViewGroup.MarginLayoutParams>()
?.endMargin = 0
llExtra.addView(box)
}

View File

@ -268,7 +268,7 @@ class SideMenuAdapter(
Action_Account.timeline(this, defaultInsertPosition, ColumnType.BOOKMARKS)
},
Item(icon = R.drawable.ic_face, title = R.string.fedibird_reactions) {
Action_Account.getReactionableAccounts(this){ list->
Action_Account.getReactionableAccounts(this,allowMisskey = false){ list->
val columnType = ColumnType.REACTIONS
AccountPicker.pick(
this,

View File

@ -373,7 +373,11 @@ object Action_Account {
}.show()
}
fun getReactionableAccounts(activity:ActMain,block:(ArrayList<SavedAccount>)->Unit){
fun getReactionableAccounts(
activity:ActMain,
allowMisskey:Boolean = true,
block:(ArrayList<SavedAccount>)->Unit
){
TootTaskRunner(activity).run(object : TootTask {
var list : List<SavedAccount>? = null
override suspend fun background(client: TootApiClient): TootApiResult? {
@ -381,7 +385,7 @@ object Action_Account {
when {
client.isApiCancelled -> false
a.isPseudo -> false
a.isMisskey -> true
a.isMisskey -> allowMisskey
else -> {
val(ti,ri)=TootInstance.getEx(client,account=a)
if(ti==null) {

View File

@ -1328,7 +1328,6 @@ object Action_Toot {
} else {
emoji.shortcode
}
else -> error("unknown emoji type")
}
addReaction(activity, column, status, newCode)
}.show()

View File

@ -3,6 +3,7 @@ package jp.juggler.subwaytooter.api.entity
import android.text.Spannable
import android.text.SpannableStringBuilder
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.ColumnViewHolder
import jp.juggler.subwaytooter.Pref
import jp.juggler.subwaytooter.emoji.CustomEmoji
import jp.juggler.subwaytooter.span.NetworkEmojiSpan
@ -33,7 +34,8 @@ class TootReaction(
// (fedibird絵文字リアクション) userストリームのemoji_reactionイベントで設定される。
val status_id: EntityId? = null,
) {
) {
companion object {
fun appendDomain(name: String, domain: String?) =
@ -94,32 +96,51 @@ class TootReaction(
val UNKNOWN = TootReaction(name = "?")
fun isUnicodeEmoji(code:String):Boolean =
code.any{ it.code >= 0x7f}
fun isUnicodeEmoji(code: String): Boolean =
code.any { it.code >= 0x7f }
fun splitEmojiDomain(code:String):Pair<String?,String?>{
fun splitEmojiDomain(code: String): Pair<String?, String?> {
// unicode絵文字ならnull,nullを返す
if( isUnicodeEmoji(code)) return Pair(null,null)
if (isUnicodeEmoji(code)) return Pair(null, null)
// Misskeyカスタム絵文字リアクションのコロンを除去
val a = code.replace(":","")
val a = code.replace(":", "")
val idx = a.indexOf("@")
return if(idx==-1) Pair(a,null) else Pair(a.substring(0,idx),a.substring(idx+1))
return if (idx == -1) Pair(a, null) else Pair(a.substring(0, idx), a.substring(idx + 1))
}
fun canReaction(
access_info:SavedAccount,
ti:TootInstance? =TootInstance.getCached(access_info.apiHost)
access_info: SavedAccount,
ti: TootInstance? = TootInstance.getCached(access_info.apiHost)
) = when {
access_info.isPseudo -> false
access_info.isMisskey -> true
else -> ti?.fedibird_capabilities?.contains("emoji_reaction") == true
}
fun decodeEmojiQuery(jsonText: String?): List<TootReaction> =
jsonText.notEmpty()?.let { src ->
try {
src.decodeJsonArray().objectList().map { parseFedibird(it) }
} catch (ex: Throwable) {
ColumnViewHolder.log.e(ex, "updateReactionQueryView: decode failed.")
null
}
} ?: emptyList()
fun encodeEmojiQuery(src: List<TootReaction>): String =
jsonArray {
for (reaction in src) {
add(reaction.jsonFedibird())
}
}.toString()
}
private val isUnicodeEmoji: Boolean
get()= isUnicodeEmoji(name)
get() = isUnicodeEmoji(name)
fun splitEmojiDomain()=
fun splitEmojiDomain() =
splitEmojiDomain(name)
override fun equals(other: Any?): Boolean =
@ -177,7 +198,7 @@ class TootReaction(
val cols = customCode.split("@", limit = 2)
val key = cols.elementAtOrNull(0)
val domain = cols.elementAtOrNull(1)
if( key != null) {
if (key != null) {
if (domain == null || domain == "" || domain == "." || domain == accessInfo?.apDomain?.ascii) {
// デコードオプションのアカウント情報と同じドメインの絵文字なら、
// そのドメインの絵文字一覧を取得済みなら
@ -207,6 +228,15 @@ class TootReaction(
// unicode絵文字、もしくは :xxx: などのshortcode表現
return EmojiDecoder.decodeEmoji(options, code)
}
// リアクションカラムの絵文字絞り込み用
fun jsonFedibird() = jsonObject {
val (basename, domain) = splitEmojiDomain()
put("name", basename ?: name)
putNotNull("domain", domain)
putNotNull("url", url)
putNotNull("static_url", static_url)
}
}
class TootReactionSet(val isMisskey: Boolean) : LinkedList<TootReaction>() {

View File

@ -6,6 +6,7 @@ import com.omadahealth.github.swipyrefreshlayout.library.SwipyRefreshLayout
import jp.juggler.subwaytooter.view.*
import org.jetbrains.anko.custom.ankoView
import androidx.appcompat.view.ContextThemeWrapper
import com.google.android.flexbox.FlexboxLayout
import jp.juggler.subwaytooter.R
// Anko Layout中にカスタムビューを指定する為に拡張関数を定義する
@ -40,3 +41,6 @@ inline fun ViewManager.recyclerView(init : RecyclerView.() -> Unit) : RecyclerVi
}, theme = 0, init = init)
}
inline fun ViewManager.flexboxLayout(init : FlexboxLayout.() -> Unit) : FlexboxLayout {
return ankoView({ FlexboxLayout(it) }, theme = 0, init = init)
}

View File

@ -1083,5 +1083,6 @@
<string name="reactions">リアクション</string>
<string name="cant_reaction_remote_custom_emoji">リモートのカスタム絵文字でリアクションできません %1$s</string>
<string name="keep_reaction_space">リアクション表示用の空間を確保する</string>
<string name="long_tap_to_delete">長押しで削除</string>
</resources>

View File

@ -1097,4 +1097,5 @@
<string name="reactions">Reactions</string>
<string name="cant_reaction_remote_custom_emoji">can\'t reaction with remote custom emoji %1$s</string>
<string name="keep_reaction_space">Keep Spacing for Reactions</string>
<string name="long_tap_to_delete">long tap to delete</string>
</resources>