Misskeyのカスタム絵文字の入力補完でaliasesに対応。Misskeyのプロフカラムで情報が足りない問題に対応。

This commit is contained in:
tateisu 2018-11-06 10:29:33 +09:00
parent 0c00e5060c
commit 9c12e5f85d
9 changed files with 324 additions and 133 deletions

View File

@ -12,6 +12,7 @@ import jp.juggler.emoji.EmojiMap201709
import jp.juggler.subwaytooter.action.Action_Follow
import jp.juggler.subwaytooter.action.Action_User
import jp.juggler.subwaytooter.api.MisskeyAccountDetailMap
import jp.juggler.subwaytooter.api.entity.TootAccount
import jp.juggler.subwaytooter.api.entity.TootAccountRef
import jp.juggler.subwaytooter.api.entity.TootStatus
@ -129,6 +130,7 @@ internal class ViewHolderHeaderProfile(
override fun bindData(column : Column) {
super.bindData(column)
if(! activity.timeline_font_size_sp.isNaN()) {
tvMovedName.textSize = activity.timeline_font_size_sp
tvMoved.textSize = activity.timeline_font_size_sp
@ -142,6 +144,13 @@ internal class ViewHolderHeaderProfile(
val whoRef = column.who_account
this.whoRef = whoRef
val who = whoRef?.get()
// Misskeyの場合はNote中のUserエンティティと /api/users/show の情報量がかなり異なる
val whoDetail = if(who == null) {
null
} else {
MisskeyAccountDetailMap.get(access_info, who.id)
}
showColor()
@ -184,7 +193,7 @@ internal class ViewHolderHeaderProfile(
access_info.supplyBaseUrl(who.avatar)
)
val name = whoRef.decoded_display_name
val name = whoDetail?.decodeDisplayName(activity) ?: whoRef.decoded_display_name
tvDisplayName.text = name
name_invalidator.register(name)
@ -193,7 +202,7 @@ internal class ViewHolderHeaderProfile(
val sb = SpannableStringBuilder()
sb.append("@").append(access_info.getFullAcct(who))
if(who.locked) {
if(whoDetail?.locked ?: who.locked) {
sb.append(" ")
val start = sb.length
sb.append("locked")
@ -208,7 +217,7 @@ internal class ViewHolderHeaderProfile(
)
}
}
if(who.bot){
if(who.bot) {
sb.append(" ")
val start = sb.length
sb.append("bot")
@ -229,9 +238,12 @@ internal class ViewHolderHeaderProfile(
tvNote.text = note
note_invalidator.register(note)
btnStatusCount.text = activity.getString(R.string.statuses) + "\n" + who.statuses_count
btnFollowing.text = activity.getString(R.string.following) + "\n" + who.following_count
btnFollowers.text = activity.getString(R.string.followers) + "\n" + who.followers_count
btnStatusCount.text = activity.getString(R.string.statuses) + "\n" +
(whoDetail?.statuses_count ?: who.statuses_count)
btnFollowing.text = activity.getString(R.string.following) + "\n" +
(whoDetail?.following_count ?: who.following_count)
btnFollowers.text = activity.getString(R.string.followers) + "\n" +
(whoDetail?.followers_count ?: who.followers_count)
val relation = UserRelation.load(access_info.db_id, who.id)
Styler.setFollowIcon(activity, btnFollow, ivFollowedBy, relation, who)
@ -259,8 +271,7 @@ internal class ViewHolderHeaderProfile(
short = true,
emojiMapProfile = who.profile_emojis
)
val content_color = column.content_color
val c = if(content_color != 0) content_color else default_color
@ -295,18 +306,18 @@ internal class ViewHolderHeaderProfile(
)
val valueText = decodeOptions.decodeHTML(item.value)
if(item.verified_at > 0L){
if(item.verified_at > 0L) {
valueText.append('\n')
val start = valueText.length
valueText.append( activity.getString(R.string.verified_at))
valueText.append( ": ")
valueText.append(TootStatus.formatTime(activity,item.verified_at,false))
valueText.append(activity.getString(R.string.verified_at))
valueText.append(": ")
valueText.append(TootStatus.formatTime(activity, item.verified_at, false))
val end = valueText.length
valueText.setSpan(
ForegroundColorSpan(Color.BLACK or 0x7fbc99)
,start,end,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
@ -320,7 +331,7 @@ internal class ViewHolderHeaderProfile(
valueView.typeface = valueTypeface
valueView.movementMethod = MyLinkMovementMethod
if(item.verified_at > 0L){
if(item.verified_at > 0L) {
valueView.setBackgroundColor(0x337fbc99)
}

View File

@ -0,0 +1,41 @@
package jp.juggler.subwaytooter.api
import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.table.SavedAccount
import java.util.concurrent.ConcurrentHashMap
object MisskeyAccountDetailMap {
private class AccountKey(
val db_id : Long,
val id : EntityId
) {
override fun hashCode() : Int {
val h1 = (db_id xor db_id.ushr(32)).toInt()
val h2 = id.hashCode()
return (h1 xor h2)
}
override fun equals(other : Any?) : Boolean {
return other is AccountKey && other.db_id == db_id && other.id == id
}
}
private val accountMap = ConcurrentHashMap<AccountKey, TootAccount>()
fun fromAccount(parser : TootParser, src : TootAccount, id : EntityId) {
// SavedAccountが不明なら何もしない
val access_info = parser.linkHelper as? SavedAccount ?: return
// アカウントのjsonがフォロー数を含まないなら何もしない
if((src.followers_count ?: - 1) < 0L) return
val key = AccountKey(access_info.db_id, id)
accountMap[key] = src
}
fun get(accessInfo : SavedAccount, id : EntityId) : TootAccount? {
return accountMap[AccountKey(accessInfo.db_id, id)]
}
}

View File

@ -19,6 +19,7 @@ class TootParser(
var misskeyDecodeProfilePin :Boolean = false
) {
val misskeyUserRelationMap = HashMap<EntityId, UserRelation>()
val misskeyAccountDetailMap = HashMap<EntityId, TootAccount>()
init{
if(linkHelper.isMisskey) serviceType = ServiceType.MISSKEY

View File

@ -2,15 +2,25 @@ package jp.juggler.subwaytooter.api.entity
import jp.juggler.subwaytooter.util.notEmptyOrThrow
import jp.juggler.subwaytooter.util.parseString
import org.json.JSONArray
import org.json.JSONObject
class CustomEmoji(
val shortcode : String, // shortcode (コロンを含まない)
val url : String, // 画像URL
val static_url : String? // アニメーションなしの画像URL
val static_url : String?, // アニメーションなしの画像URL
val aliases : ArrayList<String>? = null,
val alias:String? =null
) : Mappable<String> {
fun makeAlias(alias : String) = CustomEmoji (
shortcode= this.shortcode,
url = this.url,
static_url = this.static_url,
alias = alias
)
override val mapKey : String
get() = shortcode
@ -24,31 +34,31 @@ class CustomEmoji(
}
val decodeMisskey : (JSONObject) -> CustomEmoji = { src ->
val url = src.parseString("url") ?: error("missing url")
val name = src.parseString("name") ?: error("missing name")
// 使い方が分からない val aliases = parseAliases(src.optJSONArray("aliases"))
CustomEmoji(
shortcode = name,
shortcode = src.parseString("name") ?: error("missing name"),
url = url,
static_url = url
static_url = url,
aliases = parseAliases(src.optJSONArray("aliases"))
)
}
// private fun parseAliases(src : JSONArray?) : ArrayList<String>? {
// var dst = null as ArrayList<String>?
// if(src != null) {
// val size = src.length()
// for(i in 0 until size) {
// val str = src.parseString(i) ?: continue
// if(str.isNotEmpty()) {
// if(dst == null) dst = ArrayList(size)
// dst.add(str)
// }
// }
// }
// return dst
// }
private fun parseAliases(src : JSONArray?) : ArrayList<String>? {
var dst = null as ArrayList<String>?
if(src != null) {
val size = src.length()
if( size > 0){
dst = ArrayList(size)
for(i in 0 until size) {
val str = src.parseString(i) ?: continue
if(str.isNotEmpty()) {
dst.add(str)
}
}
}
}
return if(dst?.isNotEmpty() == true ) dst else null
}
}
}

View File

@ -3,15 +3,13 @@ package jp.juggler.subwaytooter.api.entity
import android.content.Context
import android.net.Uri
import android.text.Spannable
import jp.juggler.subwaytooter.api.MisskeyAccountDetailMap
import jp.juggler.subwaytooter.api.TootParser
import jp.juggler.subwaytooter.table.UserRelation
import jp.juggler.subwaytooter.table.UserRelationMisskey
import jp.juggler.subwaytooter.util.*
import org.json.JSONArray
import org.json.JSONObject
import java.util.ArrayList
import java.util.*
import java.util.regex.Pattern
open class TootAccount(parser : TootParser, src : JSONObject) {
@ -252,7 +250,7 @@ open class TootAccount(parser : TootParser, src : JSONObject) {
}
UserRelationMisskey.fromAccount(parser,src,id)
MisskeyAccountDetailMap.fromAccount(parser,this,id)
} else {

View File

@ -27,7 +27,11 @@ class CustomEmojiLister(internal val context : Context) {
get() = SystemClock.elapsedRealtime()
}
internal class CacheItem(val instance : String, var list : ArrayList<CustomEmoji>?) {
internal class CacheItem(
val instance : String,
var list : ArrayList<CustomEmoji>? = null,
var listWithAliases : ArrayList<CustomEmoji>? = null
) {
// 参照された時刻
var time_used : Long = 0
@ -44,6 +48,7 @@ class CustomEmojiLister(internal val context : Context) {
internal class Request(
val instance : String,
val isMisskey : Boolean,
val reportWithAliases : Boolean = false,
val onListLoaded : (list : ArrayList<CustomEmoji>) -> Unit?
)
@ -53,7 +58,7 @@ class CustomEmojiLister(internal val context : Context) {
// エラーキャッシュ
internal val cache_error = ConcurrentHashMap<String, Long>()
private val cache_error_item = CacheItem("error", null)
private val cache_error_item = CacheItem("error")
// ロード要求
internal val queue = ConcurrentLinkedQueue<Request>()
@ -100,7 +105,36 @@ class CustomEmojiLister(internal val context : Context) {
if(item != null) return item.list
}
queue.add(Request(instance, isMisskey, onListLoaded))
queue.add(Request(instance, isMisskey, onListLoaded = onListLoaded))
worker.notifyEx()
} catch(ex : Throwable) {
log.trace(ex)
}
return null
}
fun getListWithAliases(
_instance : String,
isMisskey : Boolean,
onListLoaded : (list : ArrayList<CustomEmoji>) -> Unit
) : ArrayList<CustomEmoji>? {
try {
if(_instance.isEmpty()) return null
val instance = _instance.toLowerCase()
synchronized(cache) {
val item = getCached(elapsedTime, instance)
if(item != null) return item.listWithAliases
}
queue.add(
Request(
instance,
isMisskey,
reportWithAliases = true,
onListLoaded = onListLoaded
)
)
worker.notifyEx()
} catch(ex : Throwable) {
log.trace(ex)
@ -141,8 +175,9 @@ class CustomEmojiLister(internal val context : Context) {
val item = getCached(elapsedTime, request.instance)
return@synchronized if(item != null) {
val list = item.list
if(list != null) {
fireCallback(request, list)
val listWithAliases = item.listWithAliases
if(list != null && listWithAliases != null) {
fireCallback(request, list, listWithAliases)
}
true
} else {
@ -154,6 +189,7 @@ class CustomEmojiLister(internal val context : Context) {
if(cached) continue
var list : ArrayList<CustomEmoji>? = null
var listWithAlias : ArrayList<CustomEmoji>? = null
try {
val data = if(request.isMisskey) {
App1.getHttpCachedString("https://" + request.instance + "/api/meta") { builder ->
@ -165,29 +201,32 @@ class CustomEmojiLister(internal val context : Context) {
} else {
App1.getHttpCachedString("https://" + request.instance + "/api/v1/custom_emojis")
}
if(data != null) {
list = decodeEmojiList(data, request.instance, request.isMisskey)
val a = decodeEmojiList(data, request.instance, request.isMisskey)
list = a
listWithAlias = makeListWithAlias(a)
}
} catch(ex : Throwable) {
log.trace(ex)
}
synchronized(cache) {
val now = elapsedTime
if(list == null) {
if(list == null || listWithAlias == null) {
cache_error.put(request.instance, now)
} else {
var item : CacheItem? = cache[request.instance]
if(item == null) {
item = CacheItem(request.instance, list)
item = CacheItem(request.instance, list, listWithAlias)
cache[request.instance] = item
} else {
item.list = list
item.listWithAliases = listWithAlias
item.time_update = now
}
fireCallback(request, list)
fireCallback(request, list, listWithAlias)
}
}
} catch(ex : Throwable) {
@ -197,8 +236,21 @@ class CustomEmojiLister(internal val context : Context) {
}
}
private fun fireCallback(request : Request, list : ArrayList<CustomEmoji>) {
handler.post { request.onListLoaded(list) }
private fun fireCallback(
request : Request,
list : ArrayList<CustomEmoji>,
listWithAliases : ArrayList<CustomEmoji>
) {
handler.post {
request.onListLoaded(
if(request.reportWithAliases) {
listWithAliases
} else {
list
}
)
}
}
// キャッシュの掃除
@ -243,6 +295,23 @@ class CustomEmojiLister(internal val context : Context) {
null
}
}
private fun makeListWithAlias(list : ArrayList<CustomEmoji>?) : ArrayList<CustomEmoji> {
val dst = ArrayList<CustomEmoji>()
if( list != null) {
dst.addAll(list)
for(item in list) {
val aliases = item.aliases ?: continue
for(alias in aliases) {
if( alias.equals(item.shortcode,ignoreCase = true)) continue
dst.add(item.makeAlias(alias))
}
}
dst.sortWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.alias ?: it.shortcode })
}
return dst
}
}
}

View File

@ -36,21 +36,21 @@ object EmojiDecoder {
return when(cp) {
- 1 -> true
cpColon -> false
// rubyの (Letter | Mark | Decimal_Number) はNG
// ftp://unicode.org/Public/5.1.0/ucd/UCD.html#General_Category_Values
// rubyの (Letter | Mark | Decimal_Number) はNG
// ftp://unicode.org/Public/5.1.0/ucd/UCD.html#General_Category_Values
else -> when(java.lang.Character.getType(cp).toByte()) {
// Letter
// LCはエイリアスなので文字から得られることはないはず
// Letter
// LCはエイリアスなので文字から得られることはないはず
Character.UPPERCASE_LETTER,
Character.LOWERCASE_LETTER,
Character.TITLECASE_LETTER,
Character.MODIFIER_LETTER,
Character.OTHER_LETTER -> false
// Mark
// Mark
Character.NON_SPACING_MARK,
Character.COMBINING_SPACING_MARK,
Character.ENCLOSING_MARK -> false
// Decimal_Number
// Decimal_Number
Character.DECIMAL_DIGIT_NUMBER -> false
else -> true
@ -106,7 +106,7 @@ object EmojiDecoder {
sb.append(text)
val end = sb.length
sb.setSpan(
NetworkEmojiSpan(url,scale = options.enlargeCustomEmoji),
NetworkEmojiSpan(url, scale = options.enlargeCustomEmoji),
start,
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
@ -177,7 +177,7 @@ object EmojiDecoder {
val c = s[i ++]
sb.append(
when(c) {
// https://github.com/tateisu/SubwayTooter/issues/69
// https://github.com/tateisu/SubwayTooter/issues/69
'\u00AD' -> '-'
else -> c
}

View File

@ -18,6 +18,7 @@ import java.util.ArrayList
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.Styler
import jp.juggler.subwaytooter.view.MyEditText
import java.util.regex.Pattern
@SuppressLint("InflateParams")
internal class PopupAutoCompleteAcct(
@ -30,6 +31,9 @@ internal class PopupAutoCompleteAcct(
companion object {
internal val log = LogCategory("PopupAutoCompleteAcct")
// 絵文字ショートコードにマッチするとても雑な正規表現
private val reLastShortCode = Pattern.compile(""":([^\s:]+):\z""")
}
private val acct_popup : PopupWindow
@ -135,7 +139,7 @@ internal class PopupAutoCompleteAcct(
if(acct[0] == ' ') {
// 絵文字ショートコード
if(! EmojiDecoder.canStartShortCode(sb, start)) sb.append(' ')
sb.append(acct.subSequence(2, acct.length))
sb.append( findShortCode(acct.toString()))
} else {
// @user@host, #hashtag
// 直後に空白を付与する
@ -159,6 +163,14 @@ internal class PopupAutoCompleteAcct(
updatePosition()
}
private fun findShortCode(acct : String) : String {
val m = reLastShortCode.matcher(acct)
if(m.find()) return m.group(0)
return acct
}
fun updatePosition() {
val location = IntArray(2)

View File

@ -6,6 +6,7 @@ import android.os.SystemClock
import android.support.v7.app.AlertDialog
import android.support.v7.app.AppCompatActivity
import android.text.*
import android.text.style.ForegroundColorSpan
import android.view.View
import jp.juggler.emoji.EmojiMap201709
@ -19,6 +20,7 @@ import java.util.regex.Pattern
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.Pref
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.Styler
import jp.juggler.subwaytooter.api.TootApiClient
import jp.juggler.subwaytooter.api.TootApiResult
import jp.juggler.subwaytooter.api.TootTask
@ -159,7 +161,7 @@ class PostHelper(
if(! bConfirmTag) {
if( !account.isMisskey
if(! account.isMisskey
&& visibility != TootVisibility.Public
&& reTag.matcher(content).find()
) {
@ -228,18 +230,18 @@ class PostHelper(
var credential_tmp : TootAccount? = null
val parser = TootParser(activity, account )
val parser = TootParser(activity, account)
fun getInstanceInformation(client : TootApiClient) : TootApiResult? {
val result = if( account.isMisskey){
val params = JSONObject().apply{
put("dummy",1)
val result = if(account.isMisskey) {
val params = JSONObject().apply {
put("dummy", 1)
}
client.request("/api/meta",params.toPostRequestBuilder())
}else{
client.request("/api/meta", params.toPostRequestBuilder())
} else {
client.request("/api/v1/instance")
}
instance_tmp = parseItem(::TootInstance,parser , result?.jsonObject)
instance_tmp = parseItem(::TootInstance, parser, result?.jsonObject)
return result
}
@ -255,15 +257,15 @@ class PostHelper(
// 元の投稿を削除する
if(redraft_status_id != null) {
result = if( isMisskey ){
val params = account.putMisskeyApiToken(JSONObject()).apply{
put("noteId",redraft_status_id)
result = if(isMisskey) {
val params = account.putMisskeyApiToken(JSONObject()).apply {
put("noteId", redraft_status_id)
}
client.request(
"/api/notes/delete",
params.toPostRequestBuilder()
)
}else{
} else {
client.request(
"/api/v1/statuses/$redraft_status_id",
Request.Builder().delete()
@ -283,16 +285,17 @@ class PostHelper(
account.instance = instance
}
if( visibility == TootVisibility.WebSetting) {
visibility_checked = if(account.isMisskey || instance.versionGE(TootInstance.VERSION_1_6)) {
null
} else {
val r2 = getCredential(client)
val credential_tmp = this.credential_tmp ?: return r2
val privacy = credential_tmp.source?.privacy
?: return TootApiResult(activity.getString(R.string.cant_get_web_setting_visibility))
TootVisibility.parseMastodon(privacy)
}
if(visibility == TootVisibility.WebSetting) {
visibility_checked =
if(account.isMisskey || instance.versionGE(TootInstance.VERSION_1_6)) {
null
} else {
val r2 = getCredential(client)
val credential_tmp = this.credential_tmp ?: return r2
val privacy = credential_tmp.source?.privacy
?: return TootApiResult(activity.getString(R.string.cant_get_web_setting_visibility))
TootVisibility.parseMastodon(privacy)
}
}
val json = JSONObject()
@ -308,34 +311,43 @@ class PostHelper(
)
if(visibility_checked != null) {
if( visibility_checked == TootVisibility.DirectSpecified ){
if(visibility_checked == TootVisibility.DirectSpecified) {
val userIds = JSONArray()
val reMention = Pattern.compile("(?:\\A|\\s)@([a-zA-Z0-9_]{1,20})(?:@([\\w.:-]+))?(?:\\z|\\s)")
val reMention =
Pattern.compile("(?:\\A|\\s)@([a-zA-Z0-9_]{1,20})(?:@([\\w.:-]+))?(?:\\z|\\s)")
val m = reMention.matcher(content)
while(m.find()){
while(m.find()) {
val username = m.group(1)
val host = m.group(2)
val queryParams = account.putMisskeyApiToken(JSONObject())
if(username?.isNotEmpty()==true) queryParams.put("username",username)
if(host?.isNotEmpty()==true) queryParams.put("host",host)
result = client.request("/api/users/show",queryParams.toPostRequestBuilder())
if(username?.isNotEmpty() == true) queryParams.put(
"username",
username
)
if(host?.isNotEmpty() == true) queryParams.put("host", host)
result = client.request(
"/api/users/show",
queryParams.toPostRequestBuilder()
)
val id = result?.jsonObject?.parseString("id")
if( id?.isNotEmpty() == true ){
userIds.put( id)
if(id?.isNotEmpty() == true) {
userIds.put(id)
}
}
json.put("visibility",if( userIds.length() == 0 ){
"private"
}else{
json.put("visibleUserIds",userIds)
"specified"
})
}else {
json.put("visibility",visibility_checked.strMisskey)
json.put(
"visibility", if(userIds.length() == 0) {
"private"
} else {
json.put("visibleUserIds", userIds)
"specified"
}
)
} else {
json.put("visibility", visibility_checked.strMisskey)
}
}
if(spoiler_text?.isNotEmpty() == true ) {
if(spoiler_text?.isNotEmpty() == true) {
json.put(
"cw",
EmojiDecoder.decodeShortCode(
@ -349,7 +361,7 @@ class PostHelper(
json.put("replyId", in_reply_to_id.toString())
}
json.put("viaMobile",true)
json.put("viaMobile", true)
if(attachment_list != null) {
val array = JSONArray()
@ -359,31 +371,34 @@ class PostHelper(
array.put(a.id.toString())
// Misskeyの場合、NSFWするにはアップロード済みの画像を drive/files/update で更新する
if( bNSFW){
if(bNSFW) {
val params = account.putMisskeyApiToken(JSONObject())
.put("fileId",a.id.toString())
.put("isSensitive",true)
val r = client.request("/api/drive/files/update",params.toPostRequestBuilder())
if(r==null|| r.error!=null ) return r
.put("fileId", a.id.toString())
.put("isSensitive", true)
val r = client.request(
"/api/drive/files/update",
params.toPostRequestBuilder()
)
if(r == null || r.error != null) return r
}
}
if( array.length() > 0) json.put("mediaIds", array)
if(array.length() > 0) json.put("mediaIds", array)
}
if(enquete_items?.isNotEmpty() == true) {
val choices = JSONArray().apply {
for(item in enquete_items) {
val text =EmojiDecoder.decodeShortCode(
val text = EmojiDecoder.decodeShortCode(
item,
emojiMapCustom = emojiMapCustom
)
if( text.isEmpty() ) continue
if(text.isEmpty()) continue
put(text)
}
}
if( choices.length() > 0 ) {
json.put("poll",JSONObject().apply {
put("choices",choices)
if(choices.length() > 0) {
json.put("poll", JSONObject().apply {
put("choices", choices)
})
}
}
@ -453,19 +468,21 @@ class PostHelper(
val digest = (body_string + account.acct).digestSHA256Hex()
request_builder.header("Idempotency-Key", digest)
}
result = if(isMisskey){
result = if(isMisskey) {
client.request("/api/notes/create", request_builder)
// TODO {"error":{}} が返ってきた時にどう扱えばいい?
}else{
} else {
client.request("/api/v1/statuses", request_builder)
}
val status = parser.status(if(isMisskey) {
result?.jsonObject?.optJSONObject("createdNote") ?: result?.jsonObject
}else{
result?.jsonObject
})
val status = parser.status(
if(isMisskey) {
result?.jsonObject?.optJSONObject("createdNote") ?: result?.jsonObject
} else {
result?.jsonObject
}
)
this.status = status
if(status != null) {
@ -664,18 +681,21 @@ class PostHelper(
et, last_colon, end, null, picker_caption_emoji, open_picker_emoji
)
} else {
// 絵文字を部分一致で検索
val code_list = ArrayList<CharSequence>()
val limit = 100
val s = src.substring(last_colon + 1, end).toLowerCase().replace('-', '_')
val code_list = EmojiDecoder.searchShortCode(activity, s, limit)
log.d("checkEmoji: search for %s, result=%d", s, code_list.size)
// カスタム絵文字を検索
val instance = this@PostHelper.instance
if(instance != null && instance.isNotEmpty() ) {
val custom_list = App1.custom_emoji_lister.getList(instance,isMisskey, onEmojiListLoad)
if(instance != null && instance.isNotEmpty()) {
val custom_list = App1.custom_emoji_lister.getListWithAliases(
instance,
isMisskey,
onEmojiListLoad
)
if(custom_list != null) {
val needle = src.substring(last_colon + 1, end)
for(item in custom_list) {
if(code_list.size >= limit) break
if(! item.shortcode.contains(needle)) continue
@ -689,14 +709,43 @@ class PostHelper(
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
sb.append(' ')
if(item.alias != null) {
val start = sb.length
sb.append(":")
sb.append(item.alias)
sb.append(": → ")
sb.setSpan(
ForegroundColorSpan(
Styler.getAttributeColor(
activity,
R.attr.colorTimeSmall
)
),
start,
sb.length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
sb.append(':')
sb.append(item.shortcode)
sb.append(':')
code_list.add(sb)
}
}
}
// 通常の絵文字を部分一致で検索
val remain = limit - code_list.size
if(remain > 0) {
val s = src.substring(last_colon + 1, end).toLowerCase().replace('-', '_')
val src = EmojiDecoder.searchShortCode(activity, s, remain)
log.d("checkEmoji: search for %s, result=%d", s, src.size)
code_list.addAll(src)
}
openPopup()?.setList(
et,
last_colon,
@ -731,8 +780,8 @@ class PostHelper(
this.instance = instance
this.isMisskey = isMisskey
if(instance != null ) {
App1.custom_emoji_lister.getList(instance, isMisskey,onEmojiListLoad)
if(instance != null) {
App1.custom_emoji_lister.getList(instance, isMisskey, onEmojiListLoad)
}
val popup = this.popup
@ -841,7 +890,7 @@ class PostHelper(
.appendEmoji(name, instance, bInstanceHasCustomEmoji)
val newSelection = sb.length
if(end < src_length) sb.append(src.subSequence(end, src_length) )
if(end < src_length) sb.append(src.subSequence(end, src_length))
et.text = sb
et.setSelection(newSelection)
@ -864,11 +913,11 @@ class PostHelper(
val end = Math.min(src_length, et.selectionEnd)
val sb = SpannableStringBuilder()
.append(src.subSequence(0, start) )
.append(src.subSequence(0, start))
.appendEmoji(name, instance, bInstanceHasCustomEmoji)
val newSelection = sb.length
if(end < src_length) sb.append(src.subSequence(end, src_length) )
if(end < src_length) sb.append(src.subSequence(end, src_length))
et.text = sb
et.setSelection(newSelection)