diff --git a/app/src/main/java/jp/juggler/subwaytooter/dialog/EmojiPicker.kt b/app/src/main/java/jp/juggler/subwaytooter/dialog/EmojiPicker.kt index d035d923..83ea478d 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/dialog/EmojiPicker.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/dialog/EmojiPicker.kt @@ -10,8 +10,10 @@ import android.view.* import android.widget.FrameLayout import android.widget.ImageButton import android.widget.ImageView +import android.widget.LinearLayout import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.AppCompatButton +import androidx.appcompat.widget.AppCompatImageButton import androidx.appcompat.widget.AppCompatImageView import androidx.appcompat.widget.AppCompatTextView import androidx.core.content.ContextCompat @@ -25,7 +27,6 @@ import jp.juggler.subwaytooter.databinding.EmojiPickerDialogBinding import jp.juggler.subwaytooter.emoji.* import jp.juggler.subwaytooter.pref.PrefB import jp.juggler.subwaytooter.pref.PrefS -import jp.juggler.subwaytooter.span.EmojiImageSpan import jp.juggler.subwaytooter.span.NetworkEmojiSpan import jp.juggler.subwaytooter.table.SavedAccount import jp.juggler.subwaytooter.util.emojiSizeMode @@ -37,6 +38,7 @@ import jp.juggler.util.coroutine.launchAndShowError import jp.juggler.util.data.* import jp.juggler.util.log.* import jp.juggler.util.ui.* +import org.jetbrains.anko.image import org.jetbrains.anko.wrapContent import kotlin.math.abs import kotlin.math.sign @@ -72,11 +74,16 @@ private class EmojiPicker( private open class PickerItemCategory( var name: String, val category: EmojiCategory, + val original: PickerItemCategory? = null, + var next: PickerItemCategory? = null, ) : PickerItem { val items = ArrayList() - open fun createFiltered(keywordLower: String?) = - PickerItemCategory(name = name, category = category).also { dst -> + PickerItemCategory( + name = name, + category = category, + original = this, + ).also { dst -> dst.items.addAll( if (keywordLower.isNullOrEmpty()) { items @@ -192,24 +199,63 @@ private class EmojiPicker( abstract fun bind(item: PickerItem) } + private var lastExpandCategory: PickerItemCategory? = null + private var canCollapse = true + private inner class VhCategory( - view: FrameLayout = FrameLayout(activity), + view: LinearLayout = LinearLayout(activity), ) : ViewHolderBase(view) { + var lastItem: PickerItemCategory? = null + + val ibExpand = AppCompatImageButton(activity).apply { + layoutParams = LinearLayout.LayoutParams(gridSize, matchParent) + background = ContextCompat.getDrawable( + this@EmojiPicker.activity, + R.drawable.btn_bg_transparent_round6dp + ) + setOnClickListener { + val orig = lastItem?.original + ?: return@setOnClickListener + lastExpandCategory = if (lastExpandCategory != orig) orig else lastItem?.next + // 再表示 + showFiltered( + lastSelectedCategory, + lastSelectedKeyword, + scrollCategoryTab = false, + scrollToCategory = true, + ) + } + } + val tv = AppCompatTextView(activity).apply { - layoutParams = FrameLayout.LayoutParams(headerWidth, gridSize) + layoutParams = LinearLayout.LayoutParams(0, wrapContent).apply { + weight = 1f + } + minHeightCompat = (density * 48f + 0.5f).toInt() gravity = Gravity.START or Gravity.CENTER_VERTICAL includeFontPadding = false } init { - view.layoutParams = RecyclerView.LayoutParams(wrapContent, wrapContent) + view.layoutParams = RecyclerView.LayoutParams(headerWidth, wrapContent) view.setPadding(cellMargin, cellMargin, cellMargin, cellMargin) + view.isBaselineAligned = false + view.gravity = Gravity.START or Gravity.CENTER_VERTICAL + view.addView(ibExpand) view.addView(tv) } override fun bind(item: PickerItem) { if (item is PickerItemCategory) { + lastItem = item tv.text = item.name + ibExpand.vg(canCollapse)?.let { + val drawableId = when (lastExpandCategory == item.original) { + true -> R.drawable.ic_arrow_drop_down + else -> R.drawable.ic_arrow_drop_up + } + it.image = ContextCompat.getDrawable(activity, drawableId) + } } } } @@ -405,7 +451,7 @@ private class EmojiPicker( private val density = activity.resources.displayMetrics.density val cellMargin = (density * 1f + 0.5f).toInt() val gridSize = (density * 48f + 0.5f).toInt() - val headerWidth = gridSize *6 + val headerWidth = gridSize * 6 private val cancelY = 16f private val interceptX = 40f private var tracker: VelocityTracker? = null @@ -564,27 +610,53 @@ private class EmojiPicker( private fun showFiltered( selectedCategory: EmojiCategory?, selectedKeyword: String?, + scrollCategoryTab: Boolean = true, + scrollToCategory: Boolean = false, ) { lastSelectedCategory = selectedCategory lastSelectedKeyword = selectedKeyword - adapter.list = buildList { - val keywordLower = selectedKeyword?.lowercase()?.trim() - pickerCategries.filter { + val keywordLower = selectedKeyword?.lowercase()?.trim() + this.canCollapse = + keywordLower.isNullOrEmpty() && (selectedCategory == null || selectedCategory == EmojiCategory.Custom) + + val list = buildList { + val filteredCategories = pickerCategries.filter { selectedCategory == null || it.category == selectedCategory }.mapNotNull { category -> category.createFiltered(keywordLower) .takeIf { it.items.isNotEmpty() } - }.forEach { - if (it.category == EmojiCategory.Custom || selectedCategory == null) add(it) - addAll(it.items) -// val mod = it.items.size % gridCols -// if (mod > 0) { -// repeat(gridCols - mod) { -// add(PickerItemSpace) -// } -// } + } + + for (i in filteredCategories.indices) { + filteredCategories[i].next = + filteredCategories.elementAtOrNull(i + 1)?.original + ?: filteredCategories.elementAtOrNull(i - 1)?.original + } + + if (lastExpandCategory == null || + filteredCategories.none { it.original == lastExpandCategory } + ) lastExpandCategory = filteredCategories.firstOrNull()?.original + + filteredCategories.forEach { + if (selectedCategory == null || it.category == EmojiCategory.Custom) { + // 見出し付き表示の場合は折りたたむ可能性がある + add(it) + if (!canCollapse || lastExpandCategory == it.original) addAll(it.items) + } else { + // カスタム以外のカテゴリが選択されている場合、(ヘッダがなく解除できないので) 折りたたみはできない + addAll(it.items) + } } } + adapter.list = list + if (scrollToCategory) { + val idx = + list.indexOfFirst { (it as? PickerItemCategory)?.original == lastExpandCategory } + if (idx != -1) { + views.rvGrid.smoothScrollToPosition(idx) + } + } + for (it in views.llCategories.children) { val backgroundId = when (it.tag) { selectedCategory -> R.drawable.bg_button_cw @@ -592,7 +664,7 @@ private class EmojiPicker( } it.background = ContextCompat.getDrawable(it.context, backgroundId) - if (it.tag == selectedCategory) { + if (it.tag == selectedCategory && scrollCategoryTab) { val oldScrollX = views.svCategories.scrollX val visibleWidth = views.svCategories.width log.i("left=${it.left},r=${it.right},s=$oldScrollX") diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/EmojiImageRect.kt b/app/src/main/java/jp/juggler/subwaytooter/util/EmojiImageRect.kt index d06828db..a1d6cbca 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/EmojiImageRect.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/EmojiImageRect.kt @@ -1,7 +1,6 @@ package jp.juggler.subwaytooter.util import android.graphics.RectF -import androidx.collection.LruCache import jp.juggler.subwaytooter.api.entity.TootInstance import jp.juggler.subwaytooter.table.SavedAccount import jp.juggler.util.log.LogCategory @@ -26,17 +25,16 @@ fun SavedAccount?.emojiSizeMode(): EmojiSizeMode { */ class EmojiImageRect( private val sizeMode: EmojiSizeMode, - val scale: Float =1f, - val scaleRatio: Float=1f, - val descentRatio: Float=0f, + val scale: Float = 1f, + val scaleRatio: Float = 1f, + val descentRatio: Float = 0f, val maxEmojiWidth: Float, val layout: (Int, Int) -> Unit, ) { companion object { private val log = LogCategory("EmojiImageRect") - // ImageWidthCache - val imageAspectCache = LruCache(1024) + val imageAspectCache = HashMap() } val rectDst = RectF() @@ -44,7 +42,7 @@ class EmojiImageRect( var emojiHeight = 0f var transY = 0f - private var lastWidth: Float? = null + var lastWidth: Float? = null /** * lastAspect に基づいて rectDst と transY を更新する @@ -71,9 +69,7 @@ class EmojiImageRect( ) { this.emojiHeight = h val aspect = when (aspectArg) { - null -> { - imageAspectCache[url] ?: 1f - } + null -> imageAspectCache[url] ?: 1f else -> { imageAspectCache.put(url, aspectArg) aspectArg diff --git a/app/src/main/java/jp/juggler/subwaytooter/view/NetworkEmojiView.kt b/app/src/main/java/jp/juggler/subwaytooter/view/NetworkEmojiView.kt index c25a1997..2928154d 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/view/NetworkEmojiView.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/view/NetworkEmojiView.kt @@ -60,6 +60,7 @@ class NetworkEmojiView( lp.width = w lp.height = h layoutParams = lp + requestLayout() }, ) @@ -72,19 +73,16 @@ class NetworkEmojiView( this.url = url mPaint.isFilterBitmap = true invalidate() - if (url != null && initialAspect != null) { - emojiImageRect.updateRect( - url = url, - aspectArg = initialAspect, - h = defaultHeight.toFloat(), - // may call layout callback - ) - } else { - val lp = layoutParams - lp.width = defaultWidth - lp.height = defaultHeight - layoutParams = lp - } + emojiImageRect.lastWidth = null + emojiImageRect.updateRect( + url = url ?: "", + aspectArg = initialAspect, + h = defaultHeight.toFloat(), + ) + val lp = layoutParams + lp.width = (emojiImageRect.emojiWidth + 0.5f).toInt() + lp.height = (emojiImageRect.emojiHeight + 0.5f).toInt() + layoutParams = lp } override fun onDraw(canvas: Canvas) {