(Mastodon 3.0)カスタム絵文字のカテゴリ表記。カスタム絵文字ロードの並列化。

This commit is contained in:
tateisu 2019-09-25 17:42:25 +09:00
parent 7ae2f5a748
commit 40dce24593
11 changed files with 215 additions and 120 deletions

View File

@ -47,6 +47,7 @@
<w>idat</w> <w>idat</w>
<w>idempotency</w> <w>idempotency</w>
<w>ihdr</w> <w>ihdr</w>
<w>infos</w>
<w>kapt</w> <w>kapt</w>
<w>kddi</w> <w>kddi</w>
<w>kenglxn</w> <w>kenglxn</w>

View File

@ -495,7 +495,7 @@ class App1 : Application() {
} }
internal val CACHE_CONTROL = CacheControl.Builder() internal val CACHE_CONTROL = CacheControl.Builder()
.maxAge(5, TimeUnit.MINUTES) // キャッシュが新鮮であると考えられる時間 .maxAge(1, TimeUnit.DAYS) // キャッシュが新鮮であると考えられる時間
.build() .build()
fun getHttpCached(url : String) : ByteArray? { fun getHttpCached(url : String) : ByteArray? {

View File

@ -12,7 +12,8 @@ class CustomEmoji(
val static_url : String?, // アニメーションなしの画像URL val static_url : String?, // アニメーションなしの画像URL
val aliases : ArrayList<String>? = null, val aliases : ArrayList<String>? = null,
val alias : String? = null, val alias : String? = null,
val visible_in_picker : Boolean = true val visible_in_picker : Boolean = true,
val category: String? = null
) : Mappable<String> { ) : Mappable<String> {
fun makeAlias(alias : String) = CustomEmoji( fun makeAlias(alias : String) = CustomEmoji(
@ -26,14 +27,17 @@ class CustomEmoji(
get() = shortcode get() = shortcode
companion object { companion object {
val decode : (JSONObject) -> CustomEmoji = { src -> val decode : (JSONObject) -> CustomEmoji = { src ->
CustomEmoji( CustomEmoji(
shortcode = src.notEmptyOrThrow("shortcode"), shortcode = src.notEmptyOrThrow("shortcode"),
url = src.notEmptyOrThrow("url"), url = src.notEmptyOrThrow("url"),
static_url = src.parseString("static_url"), static_url = src.parseString("static_url"),
visible_in_picker = src.optBoolean("visible_in_picker", true) visible_in_picker = src.optBoolean("visible_in_picker", true),
category =src.parseString("category")
) )
} }
val decodeMisskey : (JSONObject) -> CustomEmoji = { src -> val decodeMisskey : (JSONObject) -> CustomEmoji = { src ->
val url = src.parseString("url") ?: error("missing url") val url = src.parseString("url") ?: error("missing url")

View File

@ -17,9 +17,12 @@ import com.bumptech.glide.Glide
import jp.juggler.emoji.EmojiMap import jp.juggler.emoji.EmojiMap
import jp.juggler.subwaytooter.* import jp.juggler.subwaytooter.*
import jp.juggler.subwaytooter.api.entity.CustomEmoji import jp.juggler.subwaytooter.api.entity.CustomEmoji
import jp.juggler.subwaytooter.view.HeaderGridView
import jp.juggler.subwaytooter.view.MyViewPager import jp.juggler.subwaytooter.view.MyViewPager
import jp.juggler.subwaytooter.view.NetworkEmojiView import jp.juggler.subwaytooter.view.NetworkEmojiView
import jp.juggler.util.* import jp.juggler.util.*
import org.jetbrains.anko.padding
import org.jetbrains.anko.textColor
import org.json.JSONObject import org.json.JSONObject
import java.util.* import java.util.*
@ -32,6 +35,22 @@ class EmojiPicker(
// onEmojiPickedのinstance引数は通常の絵文字ならnull、カスタム絵文字なら非null、 // onEmojiPickedのinstance引数は通常の絵文字ならnull、カスタム絵文字なら非null、
) : View.OnClickListener, ViewPager.OnPageChangeListener { ) : View.OnClickListener, ViewPager.OnPageChangeListener {
class SkinTone(val suffix_list : Array<out String>) {
companion object {
fun create(vararg suffix_list : String) : SkinTone {
return SkinTone(suffix_list)
}
}
}
internal class EmojiItem(val name : String, val instance : String?)
internal class CustomCategory(
val rangeStart : Int,
val rangeLength : Int,
val view : View
)
companion object { companion object {
internal val log = LogCategory("EmojiPicker") internal val log = LogCategory("EmojiPicker")
@ -66,7 +85,8 @@ class EmojiPicker(
private val recent_list = ArrayList<EmojiItem>() private val recent_list = ArrayList<EmojiItem>()
private val custom_list = ArrayList<EmojiItem>() private var custom_list = ArrayList<EmojiItem>()
private var custom_categories = ArrayList<CustomCategory>()
private val emoji_url_map = HashMap<String, String>() private val emoji_url_map = HashMap<String, String>()
@ -74,16 +94,6 @@ class EmojiPicker(
private val custom_page_idx : Int private val custom_page_idx : Int
class SkinTone(val suffix_list : Array<out String>) {
companion object {
fun create(vararg suffix_list : String) : SkinTone {
return SkinTone(suffix_list)
}
}
}
internal class EmojiItem(val name : String, val instance : String?)
init { init {
// recentをロードする // recentをロードする
@ -213,13 +223,52 @@ class EmojiPicker(
private fun setCustomEmojiList(list : ArrayList<CustomEmoji>?) { private fun setCustomEmojiList(list : ArrayList<CustomEmoji>?) {
if(list == null) return if(list == null) return
bInstanceHasCustomEmoji = true bInstanceHasCustomEmoji = true
custom_list.clear()
// make categories
val newList = TreeMap<String, ArrayList<EmojiItem>>()
for(emoji in list) { for(emoji in list) {
if(! emoji.visible_in_picker) continue if(! emoji.visible_in_picker) continue
custom_list.add(EmojiItem(emoji.shortcode, instance)) val category = emoji.category ?: ""
var subList = newList[category]
if(subList == null) {
subList = ArrayList()
newList[category] = subList
}
subList.add(EmojiItem(emoji.shortcode, instance))
emoji_url_map[emoji.shortcode] = emoji.url emoji_url_map[emoji.shortcode] = emoji.url
} }
pager_adapter.getPageViewHolder(custom_page_idx)?.notifyDataSetChanged() // compose categories data list
val entries = newList.entries
custom_list.clear()
custom_categories.clear()
custom_list.ensureCapacity(entries.sumBy { it.value.size })
custom_categories.ensureCapacity(entries.size)
entries.forEach {
val rangeStart = custom_list.size
custom_list.addAll(it.value)
val rangeLength = custom_list.size - rangeStart
custom_categories.add(CustomCategory(
rangeStart,
rangeLength,
TextView(activity).apply {
layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT
)
text = when(val name = it.key) {
"" -> this@EmojiPicker.activity.getString(R.string.custom_emoji)
else -> name
}
textColor =
getAttributeColor(this@EmojiPicker.activity, R.attr.colorContentText)
textSize = 16f // SP単位
padding = (resources.displayMetrics.density * 2f + 0.5f).toInt()
}
))
}
pager_adapter.getPageViewHolder(custom_page_idx)?.reloadCustomEmoji()
pager_adapter.getPageViewHolder(recent_page_idx)?.notifyDataSetChanged() pager_adapter.getPageViewHolder(recent_page_idx)?.notifyDataSetChanged()
} }
@ -293,12 +342,12 @@ class EmojiPicker(
val id = view.id val id = view.id
selected_tone = if(selected_tone == id) 0 else id selected_tone = if(selected_tone == id) 0 else id
showSkinTone() showSkinTone()
pager_adapter.eachViewHolder { _, vh -> vh.reload() } pager_adapter.eachViewHolder { _, vh -> vh.reloadSkinTone() }
} }
internal inner class EmojiPickerPage( internal inner class EmojiPickerPage(
val hasSkinTone : Boolean, val hasSkinTone : Boolean,
category_id : Int, val category_id : Int,
title_id : Int title_id : Int
) { ) {
@ -326,29 +375,45 @@ class EmojiPicker(
inner class EmojiPickerPageViewHolder(activity : Activity, root : View) : BaseAdapter(), inner class EmojiPickerPageViewHolder(activity : Activity, root : View) : BaseAdapter(),
AdapterView.OnItemClickListener { AdapterView.OnItemClickListener {
private val gridView : GridView private val gridView : HeaderGridView = root.findViewById(R.id.gridView)
private val wh : Int private val wh = (0.5f + 48f * activity.resources.displayMetrics.density).toInt()
private var page : EmojiPickerPage? = null private var page : EmojiPickerPage? = null
init {
this.gridView = root.findViewById(R.id.gridView)
gridView.adapter = this
gridView.onItemClickListener = this
this.wh = (0.5f + 48f * activity.resources.displayMetrics.density).toInt()
}
internal fun onPageCreate(page : EmojiPickerPage) { internal fun onPageCreate(page : EmojiPickerPage) {
this.page = page this.page = page
if(page.category_id != CATEGORY_CUSTOM) {
gridView.adapter = this
} else {
reloadCustomEmoji()
}
gridView.onItemClickListener = this
} }
internal fun onPageDestroy() { internal fun onPageDestroy() {
} }
internal fun reload() { internal fun reloadSkinTone() {
this.notifyDataSetChanged() val page = this.page ?: throw RuntimeException("page is not assigned")
if(page.category_id != CATEGORY_CUSTOM) {
this.notifyDataSetChanged()
}
}
fun reloadCustomEmoji() {
gridView.reset()
if(custom_categories.size >= 2) {
for(item in custom_categories) {
gridView.addHeaderView(
rangeStart = item.rangeStart,
rangeLength = item.rangeLength,
itemHeight = wh,
v = item.view,
isSelectable = false
)
}
}
gridView.adapter = this
} }
override fun getCount() : Int { override fun getCount() : Int {
@ -376,17 +441,11 @@ class EmojiPicker(
val view : View val view : View
val item = page.emoji_list[position] val item = page.emoji_list[position]
if(item.instance != null) { if(item.instance != null) {
if(viewOld == null) { view = viewOld ?: NetworkEmojiView(activity).apply {
view = NetworkEmojiView(activity) layoutParams = AbsListView.LayoutParams(wh, wh)
val lp = AbsListView.LayoutParams(wh, wh)
view.layoutParams = lp
} else {
view = viewOld
}
view.setTag(R.id.btnAbout,item)
if(view is NetworkEmojiView) {
view.setEmoji(emoji_url_map[item.name])
} }
view.setTag(R.id.btnAbout, item)
(view as? NetworkEmojiView)?.setEmoji(emoji_url_map[item.name])
} else { } else {
if(viewOld == null) { if(viewOld == null) {
view = ImageView(activity) view = ImageView(activity)
@ -395,7 +454,7 @@ class EmojiPicker(
} else { } else {
view = viewOld view = viewOld
} }
view.setTag(R.id.btnAbout,item) view.setTag(R.id.btnAbout, item)
if(view is ImageView) { if(view is ImageView) {
val name = if(page.hasSkinTone) { val name = if(page.hasSkinTone) {
applySkinTone(item.name) applySkinTone(item.name)
@ -406,50 +465,56 @@ class EmojiPicker(
val info = EmojiMap.sShortNameToEmojiInfo[name] val info = EmojiMap.sShortNameToEmojiInfo[name]
if(info != null) { if(info != null) {
val er = info.er val er = info.er
if(er.isSvg){ if(er.isSvg) {
Glide.with(activity) Glide.with(activity)
.`as`(PictureDrawable::class.java) .`as`(PictureDrawable::class.java)
.load("file:///android_asset/${er.assetsName}") .load("file:///android_asset/${er.assetsName}")
.into(view) .into(view)
}else{ } else {
Glide.with(activity) Glide.with(activity)
.load(er.drawableId) .load(er.drawableId)
.into(view) .into(view)
} }
} }
} }
} }
return view return view
} }
override fun onItemClick(adapterView : AdapterView<*>, view : View, idx : Int, l : Long) { override fun onItemClick(
adapterView : AdapterView<*>,
view : View,
idxArg : Int,
l : Long
) {
val page = this.page ?: return val page = this.page ?: return
val item = page.emoji_list[idx] val idx = gridView.findListItemIndex(idxArg)
var name = item.name
if(item.instance != null && item.instance.isNotEmpty()) { if(idx in 0 until page.emoji_list.size) {
// カスタム絵文字 val item = page.emoji_list[idx]
selected(name, item.instance) var name = item.name
} else { if(item.instance != null && item.instance.isNotEmpty()) {
// 普通の絵文字 // カスタム絵文字
EmojiMap.sShortNameToEmojiInfo[name] ?: return selected(name, item.instance)
} else {
if(page.hasSkinTone) { // 普通の絵文字
val sv = applySkinTone(name) EmojiMap.sShortNameToEmojiInfo[name] ?: return
if(EmojiMap.sShortNameToEmojiInfo[sv] != null) {
name = sv if(page.hasSkinTone) {
val sv = applySkinTone(name)
if(EmojiMap.sShortNameToEmojiInfo[sv] != null) {
name = sv
}
} }
selected(name, null)
} }
selected(name, null)
} }
} }
} }
// name はスキントーン適用済みであること // name はスキントーン適用済みであること

View File

@ -55,13 +55,16 @@ class CustomEmojiCache(internal val context : Context) {
private val queue = LinkedList<Request>() private val queue = LinkedList<Request>()
private val handler : Handler private val handler : Handler
private val worker : Worker private val workers = ArrayList<Worker>()
init { init {
handler = Handler(context.mainLooper) handler = Handler(context.mainLooper)
cache = ConcurrentHashMap() cache = ConcurrentHashMap()
worker = Worker() for(i in 0 until 4) {
worker.start() val worker = Worker(workers)
worker.start()
workers.add(worker)
}
} }
// カラムのリロードボタンを押したタイミングでエラーキャッシュをクリアする // カラムのリロードボタンを押したタイミングでエラーキャッシュをクリアする
@ -123,7 +126,7 @@ class CustomEmojiCache(internal val context : Context) {
synchronized(queue) { synchronized(queue) {
queue.addLast(Request(refDrawTarget, url, onLoadComplete)) queue.addLast(Request(refDrawTarget, url, onLoadComplete))
} }
worker.notifyEx() workers.first().notifyEx()
} catch(ex : Throwable) { } catch(ex : Throwable) {
log.trace(ex) log.trace(ex)
// たまにcache変数がなぜかnullになる端末があるらしい // たまにcache変数がなぜかnullになる端末があるらしい
@ -131,45 +134,55 @@ class CustomEmojiCache(internal val context : Context) {
return null return null
} }
private inner class Worker : WorkerBase() { private inner class Worker(waiter : Any?) : WorkerBase(waiter) {
override fun cancel() { override fun cancel() {
// このスレッドはプロセスが生きてる限りキャンセルされない // このスレッドはプロセスが生きてる限りキャンセルされない
} }
override fun run() { override fun run() {
var ts : Long
var te : Long
while(true) { while(true) {
try { try {
var queue_size : Int var queue_size : Int
val request = synchronized(queue) { val request = synchronized(queue) {
val x = if(queue.isNotEmpty()) queue.removeFirst() else null val x = if(queue.isNotEmpty()) queue.removeFirst() else null
queue_size = queue.size queue_size = queue.size
return@synchronized x x
} }
if(request == null) { if(request == null) {
if(DEBUG) log.d("wait") if(DEBUG) log.d("wait")
ts = elapsedTime
waitEx(86400000L) waitEx(86400000L)
te = elapsedTime
if(te - ts >= 200L) log.d("sleep ${te - ts}ms")
continue continue
} }
// 描画先がGCされたなら何もしない // 描画先がGCされたなら何もしない
request.refTarget.get() ?: continue request.refTarget.get() ?: continue
ts = elapsedTime
var cache_size : Int = - 1 var cache_size : Int = - 1
if(synchronized(cache) { val chache_used = synchronized(cache) {
val now = elapsedTime val now = elapsedTime
val item = getCached(now, request.url) val item = getCached(now, request.url)
if(item != null) { if(item != null) {
if(item.frames != null) { if(item.frames != null) {
fireCallback(request) fireCallback(request)
}
return@synchronized true
} }
sweep_cache(now) return@synchronized true
cache_size = cache.size }
return@synchronized false sweep_cache(now)
}) continue cache_size = cache.size
return@synchronized false
}
te = elapsedTime
if(te - ts >= 200L) log.d("cache_used? ${te - ts}ms")
if(chache_used) continue
if(DEBUG) if(DEBUG)
log.d( log.d(
@ -181,16 +194,27 @@ class CustomEmojiCache(internal val context : Context) {
var frames : ApngFrames? = null var frames : ApngFrames? = null
try { try {
ts = elapsedTime
val data = App1.getHttpCached(request.url) val data = App1.getHttpCached(request.url)
te = elapsedTime
if(te - ts >= 200L) log.d("image get? ${te - ts}ms")
if(data == null) { if(data == null) {
log.e("get failed. url=%s", request.url) log.e("get failed. url=%s", request.url)
} else { } else {
ts = elapsedTime
frames = decodeAPNG(data, request.url) frames = decodeAPNG(data, request.url)
te = elapsedTime
if(te - ts >= 200L) log.d("iamge decode? ${te - ts}ms")
} }
} catch(ex : Throwable) { } catch(ex : Throwable) {
log.trace(ex) log.trace(ex)
} }
ts = elapsedTime
synchronized(cache) { synchronized(cache) {
if(frames == null) { if(frames == null) {
cache_error.put(request.url, elapsedTime) cache_error.put(request.url, elapsedTime)
@ -207,6 +231,8 @@ class CustomEmojiCache(internal val context : Context) {
fireCallback(request) fireCallback(request)
} }
} }
te = elapsedTime
if(te - ts >= 200L) log.d("update_cache? ${te - ts}ms")
} catch(ex : Throwable) { } catch(ex : Throwable) {
log.trace(ex) log.trace(ex)
@ -251,11 +277,10 @@ class CustomEmojiCache(internal val context : Context) {
} }
} }
private fun decodeAPNG(data : ByteArray, url : String) : ApngFrames? { private fun decodeAPNG(data : ByteArray, url : String) : ApngFrames? {
try { try {
// APNGをデコード // APNGをデコード
val x = ApngFrames.parse(64){ ByteArrayInputStream(data) } val x = ApngFrames.parse(64) { ByteArrayInputStream(data) }
if(x != null) return x if(x != null) return x
// fall thru // fall thru
} catch(ex : Throwable) { } catch(ex : Throwable) {
@ -280,7 +305,7 @@ class CustomEmojiCache(internal val context : Context) {
// SVGのロードを試みる // SVGのロードを試みる
try { try {
val b = decodeSVG(url,data, 128.toFloat()) val b = decodeSVG(url, data, 128.toFloat())
if(b != null) { if(b != null) {
if(DEBUG) log.d("SVG decoded.") if(DEBUG) log.d("SVG decoded.")
return ApngFrames(b) return ApngFrames(b)
@ -296,7 +321,10 @@ class CustomEmojiCache(internal val context : Context) {
private val options = BitmapFactory.Options() private val options = BitmapFactory.Options()
private fun decodeBitmap(data : ByteArray, pixel_max : Int) : Bitmap? { private fun decodeBitmap(
data : ByteArray,
@Suppress("SameParameterValue") pixel_max : Int
) : Bitmap? {
options.inJustDecodeBounds = true options.inJustDecodeBounds = true
options.inScaled = false options.inScaled = false
options.outWidth = 0 options.outWidth = 0
@ -319,16 +347,22 @@ class CustomEmojiCache(internal val context : Context) {
return BitmapFactory.decodeByteArray(data, 0, data.size, options) return BitmapFactory.decodeByteArray(data, 0, data.size, options)
} }
private fun decodeSVG(url:String, data : ByteArray, pixelMax : Float) : Bitmap? { private fun decodeSVG(
url : String,
data : ByteArray,
@Suppress("SameParameterValue") pixelMax : Float
) : Bitmap? {
try { try {
val svg = SVG.getFromInputStream(ByteArrayInputStream(data)) val svg = SVG.getFromInputStream(ByteArrayInputStream(data))
val src_w = svg.documentWidth // the width in pixels, or -1 if there is no width available. val src_w =
val src_h = svg.documentHeight // the height in pixels, or -1 if there is no height available. svg.documentWidth // the width in pixels, or -1 if there is no width available.
val aspect = if( src_w <= 0f || src_h <=0f){ val src_h =
svg.documentHeight // the height in pixels, or -1 if there is no height available.
val aspect = if(src_w <= 0f || src_h <= 0f) {
// widthやheightの情報がない // widthやheightの情報がない
1f 1f
}else{ } else {
src_w / src_h src_w / src_h
} }
@ -337,7 +371,7 @@ class CustomEmojiCache(internal val context : Context) {
if(aspect >= 1f) { if(aspect >= 1f) {
dst_w = pixelMax dst_w = pixelMax
dst_h = pixelMax / aspect dst_h = pixelMax / aspect
}else { } else {
dst_h = pixelMax dst_h = pixelMax
dst_w = pixelMax * aspect dst_w = pixelMax * aspect
} }
@ -352,9 +386,9 @@ class CustomEmojiCache(internal val context : Context) {
svg.renderToCanvas( svg.renderToCanvas(
canvas, canvas,
if(aspect >= 1f) { if(aspect >= 1f) {
RectF(0f, h_ceil-dst_h, dst_w, dst_h) // 後半はw,hを指定する RectF(0f, h_ceil - dst_h, dst_w, dst_h) // 後半はw,hを指定する
} else { } else {
RectF(w_ceil-dst_w, 0f, dst_w , dst_h) // 後半はw,hを指定する RectF(w_ceil - dst_w, 0f, dst_w, dst_h) // 後半はw,hを指定する
} }
) )
return b return b

View File

@ -4,13 +4,11 @@ import android.content.Context
import android.os.Handler import android.os.Handler
import android.os.SystemClock import android.os.SystemClock
import jp.juggler.subwaytooter.App1 import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.api.TootApiClient
import jp.juggler.subwaytooter.api.entity.CustomEmoji import jp.juggler.subwaytooter.api.entity.CustomEmoji
import jp.juggler.subwaytooter.api.entity.parseList import jp.juggler.subwaytooter.api.entity.parseList
import jp.juggler.util.LogCategory import jp.juggler.util.LogCategory
import jp.juggler.util.toJsonArray import jp.juggler.util.toJsonArray
import jp.juggler.util.toRequestBody import jp.juggler.util.toRequestBody
import okhttp3.RequestBody
import org.json.JSONObject import org.json.JSONObject
import java.util.* import java.util.*
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
@ -33,20 +31,12 @@ class CustomEmojiLister(internal val context : Context) {
internal class CacheItem( internal class CacheItem(
val instance : String, val instance : String,
var list : ArrayList<CustomEmoji>? = null, var list : ArrayList<CustomEmoji>? = null,
var listWithAliases : ArrayList<CustomEmoji>? = null var listWithAliases : ArrayList<CustomEmoji>? = null,
) {
// 参照された時刻
var time_used : Long = 0
// ロードした時刻 // ロードした時刻
var time_update : Long = 0 var time_update : Long = elapsedTime,
// 参照された時刻
init { var time_used : Long = time_update
time_update = elapsedTime )
time_used = time_update
}
}
internal class Request( internal class Request(
val instance : String, val instance : String,
@ -101,7 +91,7 @@ class CustomEmojiLister(internal val context : Context) {
) : ArrayList<CustomEmoji>? { ) : ArrayList<CustomEmoji>? {
try { try {
if(_instance.isEmpty()) return null if(_instance.isEmpty()) return null
val instance = _instance.toLowerCase() val instance = _instance.toLowerCase(Locale.JAPAN)
synchronized(cache) { synchronized(cache) {
val item = getCached(elapsedTime, instance) val item = getCached(elapsedTime, instance)
@ -123,7 +113,7 @@ class CustomEmojiLister(internal val context : Context) {
) : ArrayList<CustomEmoji>? { ) : ArrayList<CustomEmoji>? {
try { try {
if(_instance.isEmpty()) return null if(_instance.isEmpty()) return null
val instance = _instance.toLowerCase() val instance = _instance.toLowerCase(Locale.JAPAN)
synchronized(cache) { synchronized(cache) {
val item = getCached(elapsedTime, instance) val item = getCached(elapsedTime, instance)
@ -196,7 +186,7 @@ class CustomEmojiLister(internal val context : Context) {
try { try {
val data = if(request.isMisskey) { val data = if(request.isMisskey) {
App1.getHttpCachedString("https://" + request.instance + "/api/meta") { builder -> App1.getHttpCachedString("https://" + request.instance + "/api/meta") { builder ->
builder.post( JSONObject().toRequestBody() ) builder.post(JSONObject().toRequestBody())
} }
} else { } else {
@ -237,7 +227,6 @@ class CustomEmojiLister(internal val context : Context) {
} }
} }
private fun fireCallback( private fun fireCallback(
request : Request, request : Request,
list : ArrayList<CustomEmoji>, list : ArrayList<CustomEmoji>,
@ -297,15 +286,14 @@ class CustomEmojiLister(internal val context : Context) {
} }
} }
private fun makeListWithAlias(list : ArrayList<CustomEmoji>?) : ArrayList<CustomEmoji> { private fun makeListWithAlias(list : ArrayList<CustomEmoji>?) : ArrayList<CustomEmoji> {
val dst = ArrayList<CustomEmoji>() val dst = ArrayList<CustomEmoji>()
if( list != null) { if(list != null) {
dst.addAll(list) dst.addAll(list)
for(item in list) { for(item in list) {
val aliases = item.aliases ?: continue val aliases = item.aliases ?: continue
for(alias in aliases) { for(alias in aliases) {
if( alias.equals(item.shortcode,ignoreCase = true)) continue if(alias.equals(item.shortcode, ignoreCase = true)) continue
dst.add(item.makeAlias(alias)) dst.add(item.makeAlias(alias))
} }
} }

View File

@ -1,15 +1,15 @@
package jp.juggler.subwaytooter.util package jp.juggler.subwaytooter.util
abstract class WorkerBase : Thread() { abstract class WorkerBase(private val waiter:Any?=null) : Thread() {
abstract fun cancel() abstract fun cancel()
abstract override fun run() abstract override fun run()
fun waitEx(ms : Long) { fun waitEx(ms : Long) {
WaitNotifyHelper.waitEx(this,ms) WaitNotifyHelper.waitEx(waiter ?: this,ms)
} }
fun notifyEx() { fun notifyEx() {
WaitNotifyHelper.notifyEx(this) WaitNotifyHelper.notifyEx(waiter ?:this)
} }
} }

View File

@ -46,6 +46,7 @@ class NetworkEmojiView : View {
fun setEmoji(url : String?) { fun setEmoji(url : String?) {
this.url = url this.url = url
mPaint.isFilterBitmap = true mPaint.isFilterBitmap = true
invalidate()
} }
override fun onDraw(canvas : Canvas) { override fun onDraw(canvas : Canvas) {

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<GridView <jp.juggler.subwaytooter.view.HeaderGridView
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/gridView" android:id="@+id/gridView"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -935,5 +935,6 @@
<string name="profile_directory_not_supported_on_misskey">ディレクトリはMisskeyでは利用できません</string> <string name="profile_directory_not_supported_on_misskey">ディレクトリはMisskeyでは利用できません</string>
<string name="show_in_directory">ディレクトリに表示</string> <string name="show_in_directory">ディレクトリに表示</string>
<string name="featured_hashtags">注目のハッシュタグ</string> <string name="featured_hashtags">注目のハッシュタグ</string>
<string name="custom_emoji">カスタム絵文字</string>
</resources> </resources>

View File

@ -928,5 +928,6 @@
<string name="profile_directory_not_supported_on_misskey">Profile directory is not supported on Misskey</string> <string name="profile_directory_not_supported_on_misskey">Profile directory is not supported on Misskey</string>
<string name="show_in_directory">Show in directory</string> <string name="show_in_directory">Show in directory</string>
<string name="featured_hashtags">Featured hashtags</string> <string name="featured_hashtags">Featured hashtags</string>
<string name="custom_emoji">Custom emoji</string>
</resources> </resources>