From 2c2c787e3c0d18c274033ed27275f964e9f3a46c Mon Sep 17 00:00:00 2001 From: vadiole Date: Mon, 26 Jun 2023 20:15:33 +0200 Subject: [PATCH 01/26] fix search result view --- .../ui/table/search/SearchResultView.kt | 23 ++----------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/app/src/main/kotlin/vadiole/unicode/ui/table/search/SearchResultView.kt b/app/src/main/kotlin/vadiole/unicode/ui/table/search/SearchResultView.kt index eb109ad..71dfd7f 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/table/search/SearchResultView.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/table/search/SearchResultView.kt @@ -1,37 +1,23 @@ package vadiole.unicode.ui.table.search import android.content.Context -import android.graphics.Rect -import android.graphics.drawable.ColorDrawable import androidx.recyclerview.widget.LinearLayoutManager -import vadiole.unicode.UnicodeApp.Companion.themeManager import vadiole.unicode.ui.common.CollectionItemDecoration import vadiole.unicode.ui.common.CollectionView -import vadiole.unicode.ui.theme.ThemeDelegate -import vadiole.unicode.ui.theme.key_windowDivider import vadiole.unicode.utils.extension.dp class SearchResultView( context: Context, adapter: Adapter, private val delegate: Delegate, -) : CollectionView(context), ThemeDelegate { +) : CollectionView(context) { interface Delegate { fun onStartScrolling() } private val searchLayoutManager = LinearLayoutManager(context) - private val dividerDrawable = object : ColorDrawable() { - override fun getIntrinsicHeight(): Int { - return 1 - } - override fun getPadding(padding: Rect): Boolean { - padding.set(16.dp(context), 0, 0, 0) - return true - } - } private val itemDecoration = CollectionItemDecoration(leftPadding = 14f.dp(context)) init { @@ -40,7 +26,6 @@ class SearchResultView( setItemViewCacheSize(8) setAdapter(adapter) addItemDecoration(itemDecoration) - themeManager.observe(this) } override fun onScrollStateChanged(state: Int) { @@ -48,8 +33,4 @@ class SearchResultView( delegate.onStartScrolling() } } - - override fun applyTheme() { - dividerDrawable.color = themeManager.getColor(key_windowDivider) - } -} \ No newline at end of file +} From 6d716abbeaacdc0476e5ba1654d0a2dc862f2c71 Mon Sep 17 00:00:00 2001 From: vadiole Date: Mon, 26 Jun 2023 20:16:59 +0200 Subject: [PATCH 02/26] add block selector cell --- .../ui/table/selector/BlockSelectorCell.kt | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 app/src/main/kotlin/vadiole/unicode/ui/table/selector/BlockSelectorCell.kt diff --git a/app/src/main/kotlin/vadiole/unicode/ui/table/selector/BlockSelectorCell.kt b/app/src/main/kotlin/vadiole/unicode/ui/table/selector/BlockSelectorCell.kt new file mode 100644 index 0000000..b9a1933 --- /dev/null +++ b/app/src/main/kotlin/vadiole/unicode/ui/table/selector/BlockSelectorCell.kt @@ -0,0 +1,40 @@ +package vadiole.unicode.ui.table.selector + +import android.content.Context +import android.text.TextUtils +import android.util.TypedValue +import android.view.Gravity +import android.widget.TextView +import vadiole.unicode.UnicodeApp.Companion.themeManager +import vadiole.unicode.ui.common.StateColorDrawable +import vadiole.unicode.ui.theme.ThemeDelegate +import vadiole.unicode.ui.theme.key_windowTextPrimary +import vadiole.unicode.ui.theme.keysWindowPressable +import vadiole.unicode.ui.theme.roboto_regular +import vadiole.unicode.ui.theme.statesPressable +import vadiole.unicode.utils.extension.dp + +class BlockSelectorCell(context: Context) : TextView(context), ThemeDelegate { + + private val backgroundDrawable = StateColorDrawable() + + init { + themeManager.observe(this) + setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14f) + typeface = roboto_regular + gravity = Gravity.CENTER_VERTICAL + includeFontPadding = false + setPadding(0, 0, 16.dp(context), 0) + maxLines = 2 + ellipsize = TextUtils.TruncateAt.END + background = backgroundDrawable + } + + override fun applyTheme() { + setTextColor(themeManager.getColor(key_windowTextPrimary)) + backgroundDrawable.colors = themeManager.getColors( + statesPressable, + keysWindowPressable + ) + } +} From 3d96e6f845447393e02b0e47c95a3e2b16085b80 Mon Sep 17 00:00:00 2001 From: vadiole Date: Mon, 26 Jun 2023 20:22:21 +0200 Subject: [PATCH 03/26] add block selector view --- .../ui/table/selector/BlockSelectorView.kt | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 app/src/main/kotlin/vadiole/unicode/ui/table/selector/BlockSelectorView.kt diff --git a/app/src/main/kotlin/vadiole/unicode/ui/table/selector/BlockSelectorView.kt b/app/src/main/kotlin/vadiole/unicode/ui/table/selector/BlockSelectorView.kt new file mode 100644 index 0000000..3c6bd46 --- /dev/null +++ b/app/src/main/kotlin/vadiole/unicode/ui/table/selector/BlockSelectorView.kt @@ -0,0 +1,53 @@ +package vadiole.unicode.ui.table.selector + +import android.content.Context +import android.view.ViewGroup +import androidx.recyclerview.widget.LinearLayoutManager +import vadiole.unicode.data.Block +import vadiole.unicode.ui.common.CollectionItemDecoration +import vadiole.unicode.ui.common.CollectionView +import vadiole.unicode.utils.extension.dp + +class BlockSelectorView( + context: Context, + private val blocks: List, + private val delegate: Delegate, +) : CollectionView(context) { + interface Delegate { + fun onBlockSelected(block: Block) + } + + private val blockSelectorLayoutManager = LinearLayoutManager(context).apply { + orientation = LinearLayoutManager.VERTICAL + } + + private val adapter = object : Adapter() { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Cell { + return Cell(BlockSelectorCell(context)) + } + + override fun onBindViewHolder(holder: Cell, position: Int) { + val block = blocks[position] + val cell = holder.itemView as BlockSelectorCell + cell.text = block.name + cell.setOnClickListener { + delegate.onBlockSelected(block) + } + } + + override fun getItemCount(): Int { + return blocks.size + } + } + + private val itemDecoration = CollectionItemDecoration(leftPadding = 14f.dp(context)) + + init { + recycledViewPool.setMaxRecycledViews(0, 32) + layoutManager = blockSelectorLayoutManager + setAdapter(adapter) + setItemViewCacheSize(8) + setAdapter(adapter) + addItemDecoration(itemDecoration) + } +} From d6cccd72e25a3b528df419f3e213d035df57a761 Mon Sep 17 00:00:00 2001 From: vadiole Date: Mon, 26 Jun 2023 23:53:06 +0200 Subject: [PATCH 04/26] add Squircle --- .../vadiole/unicode/ui/common/Squircle.kt | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 app/src/main/kotlin/vadiole/unicode/ui/common/Squircle.kt diff --git a/app/src/main/kotlin/vadiole/unicode/ui/common/Squircle.kt b/app/src/main/kotlin/vadiole/unicode/ui/common/Squircle.kt new file mode 100644 index 0000000..de0f7c8 --- /dev/null +++ b/app/src/main/kotlin/vadiole/unicode/ui/common/Squircle.kt @@ -0,0 +1,127 @@ +package vadiole.unicode.ui.common + +import android.graphics.Canvas +import android.graphics.Matrix +import android.graphics.Path +import android.view.View +import androidx.core.graphics.withClip +import kotlin.math.pow + +class Squircle { + var cornerRadiusPx = 0 + set(value) { + if (field == value) return + field = value + tryRecalculatePath() + } + var skipTopLeft = false + set(value) { + if (field == value) return + field = value + tryRecalculatePath() + } + var skipTopRight = false + set(value) { + if (field == value) return + field = value + tryRecalculatePath() + } + var skipBottomLeft = false + set(value) { + if (field == value) return + field = value + tryRecalculatePath() + } + var skipBottomRight = false + set(value) { + if (field == value) return + field = value + tryRecalculatePath() + } + private var targetView: View? = null + private val squirclePath = Path() + private val cornerPath = Path() + private val clippingPath = Path() + private val mirrorMatrix = Matrix() + + private val layoutChangeListener: View.OnLayoutChangeListener = object : View.OnLayoutChangeListener { + override fun onLayoutChange( + v: View, + left: Int, + top: Int, + right: Int, + bottom: Int, + oldLeft: Int, + oldTop: Int, + oldRight: Int, + oldBottom: Int, + ) { + if (top == oldTop && left == oldLeft && right == oldRight && bottom == oldBottom) return + recalculatePath(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat()) + } + } + + fun attach(view: View) { + targetView = view + view.addOnLayoutChangeListener(layoutChangeListener) + } + + fun clip(canvas: Canvas, block: Canvas.() -> Unit) { + canvas.withClip(squirclePath, block) + } + + private fun tryRecalculatePath() { + targetView?.run { + recalculatePath(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat()) + } + } + + private fun recalculatePath(left: Float, top: Float, right: Float, bottom: Float) { + val centerX = (right + left) / 2f + val centerY = (bottom + top) / 2f + + // range (0..1), ios = 0.6 + val smooth = 0.6f + val r = cornerRadiusPx + // a, b, c -> https://telegra.ph/file/a65d7a87521e9c75e7579.png + val c = 0.2929f * r + val b = (1.5f * (2f * c * c).pow(x = 1.5f) / (c * r)) + val a = r * (1 + smooth) - c - c - b + val ab = a + b + val cb = c + b + val abc = a + b + c + val abcc = a + b + c + c + + cornerPath.apply { + rewind() + moveTo(left, top) + lineTo(left, top + abcc) + rCubicTo(0f, -a, 0f, -ab, c, -abc) + rCubicTo(c, -c, cb, -c, abc, -c) + lineTo(left, top) + } + + squirclePath.apply { + rewind() + addRect(left, top, right, bottom, Path.Direction.CW) + mirrorParams.forEachIndexed { index, (scaleX, scaleY) -> + if (isCornerSkipped(index)) return@forEachIndexed + mirrorMatrix.setScale(scaleX, scaleY, centerX, centerY) + cornerPath.transform(mirrorMatrix, clippingPath) + op(clippingPath, Path.Op.DIFFERENCE) + } + } + } + + private fun isCornerSkipped(cornerIndex: Int): Boolean = when (cornerIndex) { + 0 -> skipTopLeft + 1 -> skipTopRight + 2 -> skipBottomRight + 3 -> skipBottomLeft + else -> error("Invalid corner index") + } + + companion object { + private val mirrorParams = arrayOf(1f to 1f, -1f to 1f, -1f to -1f, 1f to -1f) + } +} From 2bda3245888bca835b5e60c4948eadbe333bbe2d Mon Sep 17 00:00:00 2001 From: vadiole Date: Tue, 27 Jun 2023 16:24:52 +0200 Subject: [PATCH 05/26] add block selection --- .../unicode/ui/common/CollectionView.kt | 14 +- .../vadiole/unicode/ui/common/Squircle.kt | 16 ++ .../vadiole/unicode/ui/table/TableHelper.kt | 17 +- .../vadiole/unicode/ui/table/TableScreen.kt | 31 +++- .../ui/table/selector/BlockSelectorCell.kt | 7 +- .../ui/table/selector/BlockSelectorPopup.kt | 57 +++++++ .../ui/table/selector/BlockSelectorView.kt | 146 +++++++++++++++++- app/src/main/res/drawable/ic_anchor.xml | 9 ++ 8 files changed, 288 insertions(+), 9 deletions(-) create mode 100644 app/src/main/kotlin/vadiole/unicode/ui/table/selector/BlockSelectorPopup.kt create mode 100644 app/src/main/res/drawable/ic_anchor.xml diff --git a/app/src/main/kotlin/vadiole/unicode/ui/common/CollectionView.kt b/app/src/main/kotlin/vadiole/unicode/ui/common/CollectionView.kt index e9d06fd..46cedef 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/common/CollectionView.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/common/CollectionView.kt @@ -5,6 +5,10 @@ import android.view.View import androidx.recyclerview.widget.RecyclerView open class CollectionView(context: Context) : RecyclerView(context) { + + var isScrollEnabled = true + var fastScrollAnimationRunning = false + init { overScrollMode = View.OVER_SCROLL_ALWAYS clipToPadding = false @@ -16,4 +20,12 @@ open class CollectionView(context: Context) : RecyclerView(context) { abstract class Adapter : RecyclerView.Adapter() class Cell(cell: View) : ViewHolder(cell) -} \ No newline at end of file + + override fun canScrollVertically(direction: Int): Boolean { + return isScrollEnabled && super.canScrollVertically(direction) + } + + override fun canScrollHorizontally(direction: Int): Boolean { + return isScrollEnabled && super.canScrollHorizontally(direction) + } +} diff --git a/app/src/main/kotlin/vadiole/unicode/ui/common/Squircle.kt b/app/src/main/kotlin/vadiole/unicode/ui/common/Squircle.kt index de0f7c8..58ef22e 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/common/Squircle.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/common/Squircle.kt @@ -66,6 +66,22 @@ class Squircle { view.addOnLayoutChangeListener(layoutChangeListener) } + fun attach( + view: View, + left: Float, + top: Float, + right: Float, + bottom: Float, + ) { + targetView = view + recalculatePath(left, top, right, bottom) + } + + fun detach() { + targetView?.removeOnLayoutChangeListener(layoutChangeListener) + targetView = null + } + fun clip(canvas: Canvas, block: Canvas.() -> Unit) { canvas.withClip(squirclePath, block) } diff --git a/app/src/main/kotlin/vadiole/unicode/ui/table/TableHelper.kt b/app/src/main/kotlin/vadiole/unicode/ui/table/TableHelper.kt index 1a34301..5a7c56e 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/table/TableHelper.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/table/TableHelper.kt @@ -55,6 +55,7 @@ class TableHelper(private val unicodeStorage: UnicodeStorage, private val userCo lastBlock = block return block } + codePoint.value > last.end -> { val index = blocks.binarySearch(fromIndex = lastBlockIndex) { block -> block.contains(codePoint) @@ -64,12 +65,26 @@ class TableHelper(private val unicodeStorage: UnicodeStorage, private val userCo lastBlock = block return block } + else -> { return last } } } + fun getPosition(block: Block): Int { + if (blocks.isEmpty()) return 0 + val index = tableChars.binarySearch { codePoint -> + when { + codePoint.value < block.start -> -1 + codePoint.value > block.start -> 1 + else -> 0 + } + } + if (index < 0) return 0 + return index + } + fun getChars(position: Int, spanCount: Int): Array { val codePoints = Array(spanCount) { index -> val globalIndex = position * spanCount + index @@ -77,4 +92,4 @@ class TableHelper(private val unicodeStorage: UnicodeStorage, private val userCo } return codePoints } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/vadiole/unicode/ui/table/TableScreen.kt b/app/src/main/kotlin/vadiole/unicode/ui/table/TableScreen.kt index def8db0..89daa84 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/table/TableScreen.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/table/TableScreen.kt @@ -3,6 +3,7 @@ package vadiole.unicode.ui.table import android.content.Context import android.graphics.Canvas import android.graphics.Paint +import android.view.Gravity.LEFT import android.view.Gravity.TOP import android.view.View import android.view.ViewGroup @@ -12,6 +13,7 @@ import androidx.core.view.updatePadding import kotlinx.coroutines.Job import kotlinx.coroutines.launch import vadiole.unicode.UnicodeApp.Companion.themeManager +import vadiole.unicode.data.Block import vadiole.unicode.data.CodePoint import vadiole.unicode.ui.common.CollectionView import vadiole.unicode.ui.common.Screen @@ -20,6 +22,8 @@ import vadiole.unicode.ui.common.TopBar import vadiole.unicode.ui.table.search.SearchHelper import vadiole.unicode.ui.table.search.SearchResultCell import vadiole.unicode.ui.table.search.SearchResultView +import vadiole.unicode.ui.table.selector.BlockSelectorPopup +import vadiole.unicode.ui.table.selector.BlockSelectorView import vadiole.unicode.ui.theme.ThemeDelegate import vadiole.unicode.ui.theme.key_topBarBackground import vadiole.unicode.ui.theme.key_windowDivider @@ -30,6 +34,7 @@ import vadiole.unicode.utils.extension.matchParent import vadiole.unicode.utils.extension.navigationBars import vadiole.unicode.utils.extension.statusBars import vadiole.unicode.utils.extension.toClipboard +import vadiole.unicode.utils.extension.wrapContent class TableScreen( context: Context, @@ -42,6 +47,12 @@ class TableScreen( private val statusBarPaint = Paint().apply { style = Paint.Style.FILL } + private val blockSelectorDelegate = object : BlockSelectorView.Delegate { + override fun onBlockSelected(block: Block) { + popup?.dismiss() + tableView.scrollToPosition(tableHelper.getPosition(block) / spanCount) + } + } private val charCellDelegate = object : CharRow.Delegate { override fun onClick(codePoint: CodePoint) = delegate.onItemClick(codePoint) override fun onLongClick(codePoint: CodePoint) { @@ -81,8 +92,22 @@ class TableScreen( view.bind(data) } } + + private var popup: BlockSelectorPopup? = null + private val topBar: TopBar = TopBar(context, "Unicode") { - tableView.smoothScrollToPosition(0) + if (tableHelper.blocks.isEmpty()) return@TopBar + + val popup = popup ?: kotlin.run { + val blockSelectorView = BlockSelectorView(context, tableHelper.blocks, blockSelectorDelegate) + BlockSelectorPopup(blockSelectorView, wrapContent, wrapContent).also { + popup = it + } + } + + val xOffset = (width - popup.view.calculateWidth()) / 2 + val yOffset = (-8).dp(context) + popup.showAsDropDown(this, xOffset, yOffset, LEFT or TOP) } private val searchDelegate = object : SearchBar.Delegate { override fun onFocused(): Boolean { @@ -178,7 +203,7 @@ class TableScreen( override fun onDraw(canvas: Canvas) { super.onDraw(canvas) - canvas.drawRect(0f, 0f,measuredWidth.toFloat(), topInset.toFloat(), statusBarPaint) + canvas.drawRect(0f, 0f, measuredWidth.toFloat(), topInset.toFloat(), statusBarPaint) } override fun applyTheme() { @@ -189,4 +214,4 @@ class TableScreen( interface Delegate { fun onItemClick(codePoint: CodePoint) } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/vadiole/unicode/ui/table/selector/BlockSelectorCell.kt b/app/src/main/kotlin/vadiole/unicode/ui/table/selector/BlockSelectorCell.kt index b9a1933..266c7d3 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/table/selector/BlockSelectorCell.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/table/selector/BlockSelectorCell.kt @@ -5,6 +5,7 @@ import android.text.TextUtils import android.util.TypedValue import android.view.Gravity import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView import vadiole.unicode.UnicodeApp.Companion.themeManager import vadiole.unicode.ui.common.StateColorDrawable import vadiole.unicode.ui.theme.ThemeDelegate @@ -13,18 +14,20 @@ import vadiole.unicode.ui.theme.keysWindowPressable import vadiole.unicode.ui.theme.roboto_regular import vadiole.unicode.ui.theme.statesPressable import vadiole.unicode.utils.extension.dp +import vadiole.unicode.utils.extension.matchParent class BlockSelectorCell(context: Context) : TextView(context), ThemeDelegate { private val backgroundDrawable = StateColorDrawable() init { + layoutParams = RecyclerView.LayoutParams(matchParent, 48.dp(context)) themeManager.observe(this) - setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14f) + setTextSize(TypedValue.COMPLEX_UNIT_DIP, 17f) typeface = roboto_regular gravity = Gravity.CENTER_VERTICAL includeFontPadding = false - setPadding(0, 0, 16.dp(context), 0) + setPadding(16.dp(context), 0, 16.dp(context), 0) maxLines = 2 ellipsize = TextUtils.TruncateAt.END background = backgroundDrawable diff --git a/app/src/main/kotlin/vadiole/unicode/ui/table/selector/BlockSelectorPopup.kt b/app/src/main/kotlin/vadiole/unicode/ui/table/selector/BlockSelectorPopup.kt new file mode 100644 index 0000000..d7b8a51 --- /dev/null +++ b/app/src/main/kotlin/vadiole/unicode/ui/table/selector/BlockSelectorPopup.kt @@ -0,0 +1,57 @@ +package vadiole.unicode.ui.table.selector + +import android.content.Context +import android.view.View +import android.view.WindowManager +import android.widget.PopupWindow + +open class BlockSelectorPopup(val view: BlockSelectorView, x: Int, y: Int) : PopupWindow(view, x, y) { + init { + isFocusable = true + animationStyle = 0 + isOutsideTouchable = true + isClippingEnabled = true + } + + fun dimBehind() { + val container = contentView.rootView + val context = contentView.context + val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager + val layoutParams = (container.layoutParams as WindowManager.LayoutParams).apply { + flags = flags or WindowManager.LayoutParams.FLAG_DIM_BEHIND + dimAmount = 0.2f + } + windowManager.updateViewLayout(container, layoutParams) + } + + private fun dismissDim() { + val container = contentView.rootView + val context = contentView.context + val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager + if (container.layoutParams == null || container.layoutParams !is WindowManager.LayoutParams) { + return + } + val layoutParams = container.layoutParams as WindowManager.LayoutParams + try { + if (layoutParams.flags and WindowManager.LayoutParams.FLAG_DIM_BEHIND != 0) { + layoutParams.apply { + flags = flags and WindowManager.LayoutParams.FLAG_DIM_BEHIND.inv() + dimAmount = 0.0f + } + windowManager.updateViewLayout(container, layoutParams) + } + } catch (ignore: Exception) { + } + } + + override fun dismiss() { + dismissDim() + view.dismiss { super.dismiss() } + } + + override fun showAsDropDown(anchor: View?, xoff: Int, yoff: Int, gravity: Int) { + super.showAsDropDown(anchor, xoff, yoff, gravity) + dimBehind() + view.show() + } +} diff --git a/app/src/main/kotlin/vadiole/unicode/ui/table/selector/BlockSelectorView.kt b/app/src/main/kotlin/vadiole/unicode/ui/table/selector/BlockSelectorView.kt index 3c6bd46..033c7b1 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/table/selector/BlockSelectorView.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/table/selector/BlockSelectorView.kt @@ -1,22 +1,43 @@ package vadiole.unicode.ui.table.selector +import android.animation.ValueAnimator import android.content.Context +import android.graphics.Canvas +import android.graphics.drawable.ColorDrawable import android.view.ViewGroup +import android.view.animation.AlphaAnimation +import android.view.animation.Animation +import android.view.animation.AnimationSet +import android.view.animation.LayoutAnimationController +import android.view.animation.PathInterpolator +import android.view.animation.ScaleAnimation +import androidx.core.animation.addListener +import androidx.core.graphics.drawable.updateBounds import androidx.recyclerview.widget.LinearLayoutManager +import vadiole.unicode.R +import vadiole.unicode.UnicodeApp.Companion.themeManager import vadiole.unicode.data.Block import vadiole.unicode.ui.common.CollectionItemDecoration import vadiole.unicode.ui.common.CollectionView +import vadiole.unicode.ui.common.Squircle +import vadiole.unicode.ui.theme.ThemeDelegate +import vadiole.unicode.ui.theme.key_dialogSurface import vadiole.unicode.utils.extension.dp class BlockSelectorView( context: Context, private val blocks: List, private val delegate: Delegate, -) : CollectionView(context) { +) : CollectionView(context), ThemeDelegate { interface Delegate { fun onBlockSelected(block: Block) } + private val squircle = Squircle().apply { + cornerRadiusPx = 12.dp(context) + } + private val backgroundDrawable = ColorDrawable() + private val blockSelectorLayoutManager = LinearLayoutManager(context).apply { orientation = LinearLayoutManager.VERTICAL } @@ -41,13 +62,134 @@ class BlockSelectorView( } private val itemDecoration = CollectionItemDecoration(leftPadding = 14f.dp(context)) + private var showAnimator: ValueAnimator? = null + private val anchorDrawable = context.getDrawable(R.drawable.ic_anchor)!!.mutate() + private val topMargin = anchorDrawable.intrinsicHeight - 4.dp(context) init { + themeManager.observe(this) + background = backgroundDrawable recycledViewPool.setMaxRecycledViews(0, 32) layoutManager = blockSelectorLayoutManager - setAdapter(adapter) setItemViewCacheSize(8) setAdapter(adapter) addItemDecoration(itemDecoration) + squircle.attach(this) + setPadding(0, 8.dp(context) + topMargin, 0, 8.dp(context)) + layoutAnimation = LayoutAnimationController( + AnimationSet(true).apply { + interpolator = showInterpolator + addAnimation( + ScaleAnimation( + 0.95f, 1f, 0.95f, 1f, Animation.RELATIVE_TO_SELF, 0.1f, Animation.RELATIVE_TO_SELF, 0.5f + ).apply { + duration = 300 + } + ) + addAnimation( + AlphaAnimation( + 0.7f, 1f + ).apply { + duration = 300 + } + ) + }, + ).apply { + order = LayoutAnimationController.ORDER_NORMAL + delay = 0.04f + } + adapter.notifyDataSetChanged() + } + + override fun onMeasure(widthSpec: Int, heightSpec: Int) { + val widthMeasureSpec = MeasureSpec.makeMeasureSpec(calculateWidth(), MeasureSpec.EXACTLY) + val heightMeasureSpec = MeasureSpec.makeMeasureSpec(calculateHeight(), MeasureSpec.AT_MOST) + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + val centerX = w * 0.5f + anchorDrawable.setBounds( + centerX.toInt() - anchorDrawable.intrinsicWidth / 2, + 0, + centerX.toInt() + anchorDrawable.intrinsicWidth / 2, + anchorDrawable.intrinsicHeight + ) + } + + fun calculateWidth(): Int { + val displayWidth = context.resources.displayMetrics.widthPixels + return (500 - 16).dp(context).coerceAtMost((displayWidth * .95f).toInt()) + } + + fun calculateHeight(): Int { + val displayHeight = context.resources.displayMetrics.heightPixels + return (700 - 16 - 64).dp(context).coerceAtMost((displayHeight * .75f).toInt()) + } + + override fun draw(c: Canvas) { + drawAnchor(c) + squircle.clip(c) { + super.draw(c) + } + } + + private fun drawAnchor(c: Canvas) { + anchorDrawable.draw(c) + } + + override fun applyTheme() { + backgroundDrawable.color = themeManager.getColor(key_dialogSurface) + anchorDrawable.setTint(themeManager.getColor(key_dialogSurface)) + invalidate() + } + + fun show() { + val lastProgress = showAnimator?.animatedValue as? Float ?: 0f + showAnimator?.cancel() + showAnimator = ValueAnimator.ofFloat(lastProgress, 1f).apply { + duration = 300 + interpolator = showInterpolator + addUpdateListener { animator -> + setShowProgress(animator.animatedValue as Float, dismiss = false) + } + start() + } + } + + fun dismiss(onEnd: () -> Unit) { + val currentProgress = showAnimator?.animatedValue as? Float ?: 1f + showAnimator?.cancel() + showAnimator = ValueAnimator.ofFloat(currentProgress, 0f).apply { + duration = 300 + interpolator = showInterpolator + addUpdateListener { progress -> + setShowProgress(progress.animatedValue as Float, true) + } + addListener(onEnd = { onEnd() }) + start() + } + } + + private fun setShowProgress(progress: Float, dismiss: Boolean) { + val actualProgress = 1f - progress + alpha = progress + val translationModifier = if (dismiss) -1 else -1 + val clipModifier = if (dismiss) 0.1f else 1f + translationY = translationModifier * 8.dp(context).toFloat() * actualProgress + val horizontalOffset = (right - left) * 0.4f * actualProgress * clipModifier + val topOffset = 12.dp(context).toFloat() * actualProgress + val bottomOffset = (bottom - top) * 0.9f * actualProgress * clipModifier + val left = left + horizontalOffset + val right = right - horizontalOffset + val top = top + topOffset + topMargin + val bottom = bottom - bottomOffset + squircle.attach(this, left, top, right, bottom) + anchorDrawable.updateBounds(top = topOffset.toInt(), bottom = topOffset.toInt() + anchorDrawable.intrinsicHeight) + invalidate() + } + + companion object { + private val showInterpolator = PathInterpolator(0.23f, 1f, 0.32f, 1f) } } diff --git a/app/src/main/res/drawable/ic_anchor.xml b/app/src/main/res/drawable/ic_anchor.xml new file mode 100644 index 0000000..51aeb86 --- /dev/null +++ b/app/src/main/res/drawable/ic_anchor.xml @@ -0,0 +1,9 @@ + + + From 67d413d42834201c409a36e3d0493582039867d7 Mon Sep 17 00:00:00 2001 From: vadiole Date: Sat, 1 Jul 2023 23:08:56 +0200 Subject: [PATCH 06/26] improve performance --- .../main/kotlin/vadiole/unicode/data/Block.kt | 2 +- .../kotlin/vadiole/unicode/data/CharObj.kt | 14 +++---- .../kotlin/vadiole/unicode/data/CodePoint.kt | 40 ++++++++++++++++++- .../vadiole/unicode/data/UnicodeStorage.kt | 33 ++++++++------- .../vadiole/unicode/ui/NavigationView.kt | 12 +++--- .../unicode/ui/details/DetailsSheet.kt | 6 +-- .../vadiole/unicode/ui/table/CharRow.kt | 9 +++-- .../vadiole/unicode/ui/table/TableHelper.kt | 33 +++++++-------- .../vadiole/unicode/ui/table/TableScreen.kt | 11 ++--- .../ui/table/selector/BlockSelectorView.kt | 2 +- .../vadiole/unicode/utils/extension/Array.kt | 30 ++++++++++++++ 11 files changed, 128 insertions(+), 64 deletions(-) create mode 100644 app/src/main/kotlin/vadiole/unicode/utils/extension/Array.kt diff --git a/app/src/main/kotlin/vadiole/unicode/data/Block.kt b/app/src/main/kotlin/vadiole/unicode/data/Block.kt index 72a1622..d3f91ee 100644 --- a/app/src/main/kotlin/vadiole/unicode/data/Block.kt +++ b/app/src/main/kotlin/vadiole/unicode/data/Block.kt @@ -6,4 +6,4 @@ class Block(val id: Int, val start: Int, val end: Int, val name: String) { codePoint.value > end -> -1 else -> 0 } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/vadiole/unicode/data/CharObj.kt b/app/src/main/kotlin/vadiole/unicode/data/CharObj.kt index c6c1aec..a301ef1 100644 --- a/app/src/main/kotlin/vadiole/unicode/data/CharObj.kt +++ b/app/src/main/kotlin/vadiole/unicode/data/CharObj.kt @@ -1,16 +1,16 @@ package vadiole.unicode.data +import java.util.Locale import vadiole.unicode.utils.extension.leftPad -import java.util.* -class CharObj(val id: Int, val codePoint: Int, val name: String, val version: String, val blockName: String) { - val char: String = String(Character.toChars(codePoint)) - val hex = codePoint.toString(16).uppercase(Locale.ENGLISH).leftPad(4, '0') +class CharObj(val id: Int, val codePointRaw: Int, val name: String, val version: String, val blockName: String) { + val char: String = String(Character.toChars(codePointRaw)) + val hex = codePointRaw.toString(16).uppercase(Locale.ENGLISH).leftPad(4, '0') val infoValues: Array = arrayOf( "U+$hex", - "&#$codePoint", + "&#$codePointRaw", "\\$hex", version, ) - val link = "vadiole.github.io/unicode?c=$codePoint" -} \ No newline at end of file + val link = "vadiole.github.io/unicode?c=$codePointRaw" +} diff --git a/app/src/main/kotlin/vadiole/unicode/data/CodePoint.kt b/app/src/main/kotlin/vadiole/unicode/data/CodePoint.kt index 622dd7a..e91f3d1 100644 --- a/app/src/main/kotlin/vadiole/unicode/data/CodePoint.kt +++ b/app/src/main/kotlin/vadiole/unicode/data/CodePoint.kt @@ -4,4 +4,42 @@ package vadiole.unicode.data value class CodePoint(val value: Int) { val char: String get() = String(Character.toChars(value)) -} \ No newline at end of file +} + +@JvmInline +value class CodePointArray(val storage: IntArray) : Collection { + constructor(size: Int) : this(IntArray(size)) + + override val size: Int get() = storage.size + + override fun isEmpty() = storage.isEmpty() + + override fun iterator(): Iterator = object : Iterator { + private var index = 0 + override fun hasNext() = index < storage.size + override fun next() = if (index < storage.size) CodePoint(storage[index++]) else throw NoSuchElementException(index.toString()) + } + + override fun containsAll(elements: Collection) = elements.all { storage.contains(it.value) } + + override fun contains(element: CodePoint): Boolean = storage.contains(element.value) + + operator fun get(index: Int) = CodePoint(storage[index]) + + operator fun set(index: Int, value: CodePoint) { + storage[index] = value.value + } +} + +inline fun CodePointArray(size: Int, init: (Int) -> CodePoint): CodePointArray { + return CodePointArray(IntArray(size) { index -> (init(index)).value }) +} + +inline fun CodePointArray.filterMaybe(predicate: (CodePoint) -> Boolean): CodePointArray { + val filtered = storage.filterTo(ArrayList(size)) { predicate(CodePoint(it)) } + return CodePointArray(filtered.toIntArray()) +} + +fun CodePointArray.binarySearch(element: CodePoint, fromIndex: Int = 0, toIndex: Int = size): Int { + return java.util.Arrays.binarySearch(storage, fromIndex, toIndex, element.value) +} diff --git a/app/src/main/kotlin/vadiole/unicode/data/UnicodeStorage.kt b/app/src/main/kotlin/vadiole/unicode/data/UnicodeStorage.kt index 3e350c0..bbe6dae 100644 --- a/app/src/main/kotlin/vadiole/unicode/data/UnicodeStorage.kt +++ b/app/src/main/kotlin/vadiole/unicode/data/UnicodeStorage.kt @@ -4,8 +4,8 @@ import android.content.Context import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase.OPEN_READONLY import android.database.sqlite.SQLiteDatabase.openDatabase -import vadiole.unicode.utils.extension.io import java.io.File +import vadiole.unicode.utils.extension.io class UnicodeStorage(private val context: Context) { var database: SQLiteDatabase? = null @@ -27,7 +27,6 @@ class UnicodeStorage(private val context: Context) { context.filesDir.listFiles()!!.forEach { file -> file.deleteOnExit() } - @Suppress("BlockingMethodInNonBlockingContext") context.assets.open(databaseName).use { input -> database.outputStream().use { output -> input.copyTo(output) @@ -37,7 +36,7 @@ class UnicodeStorage(private val context: Context) { return@io database.absolutePath } - suspend fun getCodePoints(count: Int): List = io { + suspend fun getCodePoints(count: Int): CodePointArray = io { val query = if (count > 0) { "SELECT code_point FROM char WHERE id < ? LIMIT ?" } else { @@ -48,33 +47,33 @@ class UnicodeStorage(private val context: Context) { } else { null } - val result = mutableListOf() + val result: CodePointArray openDatabase().rawQuery(query, args).use { cursor -> val codePointIndex = cursor.getColumnIndex("code_point") - while (cursor.moveToNext()) { - val codePoint = CodePoint( - value = cursor.getInt(codePointIndex), - ) - result.add(codePoint) + result = CodePointArray(cursor.count) { + cursor.moveToNext() + CodePoint(value = cursor.getInt(codePointIndex)) } } + return@io result } - suspend fun getBlocks(): List = io { - val result = mutableListOf() + suspend fun getBlocks(): Array = io { + val result: Array openDatabase().rawQuery(queryGetBlocks, null).use { cursor -> val idIndex = cursor.getColumnIndex("id") val endIndex = cursor.getColumnIndex("end") val nameIndex = cursor.getColumnIndex("name") - while (cursor.moveToNext()) { - val block = Block( + var lastEnd = 0 + result = Array(cursor.count) { + cursor.moveToNext() + Block( id = cursor.getInt(idIndex), - start = (result.lastOrNull()?.end ?: -1) + 1, - end = cursor.getInt(endIndex), + start = lastEnd + 1, + end = cursor.getInt(endIndex).also { lastEnd = it }, name = cursor.getString(nameIndex), ) - result.add(block) } } return@io result @@ -92,7 +91,7 @@ class UnicodeStorage(private val context: Context) { cursor.moveToNext() result = CharObj( id = cursor.getInt(charIdIndex), - codePoint = cursor.getInt(codePointIndex), + codePointRaw = cursor.getInt(codePointIndex), name = cursor.getString(charNameIndex), version = cursor.getString(versionIndex), blockName = cursor.getString(blockNameIndex), diff --git a/app/src/main/kotlin/vadiole/unicode/ui/NavigationView.kt b/app/src/main/kotlin/vadiole/unicode/ui/NavigationView.kt index a75e92f..8f446e3 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/NavigationView.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/NavigationView.kt @@ -11,6 +11,10 @@ import androidx.core.view.doOnLayout import androidx.dynamicanimation.animation.DynamicAnimation import androidx.dynamicanimation.animation.SpringAnimation import androidx.dynamicanimation.animation.SpringForce +import kotlin.math.PI +import kotlin.math.abs +import kotlin.math.atan +import kotlin.math.hypot import vadiole.unicode.UnicodeApp.Companion.themeManager import vadiole.unicode.UnicodeApp.Companion.unicodeStorage import vadiole.unicode.UnicodeApp.Companion.userConfig @@ -26,10 +30,6 @@ import vadiole.unicode.utils.extension.frameParams import vadiole.unicode.utils.extension.isVisible import vadiole.unicode.utils.extension.matchParent import vadiole.unicode.utils.extension.with -import kotlin.math.PI -import kotlin.math.abs -import kotlin.math.atan -import kotlin.math.hypot class NavigationView(context: Context) : FrameLayout(context), ThemeDelegate { private val scaledTouchSlop = ViewConfiguration.get(context).scaledTouchSlop @@ -57,7 +57,7 @@ class NavigationView(context: Context) : FrameLayout(context), ThemeDelegate { visibility = View.GONE } private val detailsDelegate = object : DetailsSheet.Delegate { - override fun findInTable(codePoint: Int) { + override fun findInTable(codePoint: CodePoint) { hideDetailsBottomSheet() tableScreen.hideSearch() tableScreen.scrollToChar(codePoint) @@ -265,4 +265,4 @@ class NavigationView(context: Context) : FrameLayout(context), ThemeDelegate { override fun applyTheme() { dimView.setBackgroundColor(themeManager.getColor(key_dialogDim)) } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/vadiole/unicode/ui/details/DetailsSheet.kt b/app/src/main/kotlin/vadiole/unicode/ui/details/DetailsSheet.kt index 91d82c2..c481d68 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/details/DetailsSheet.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/details/DetailsSheet.kt @@ -48,7 +48,7 @@ class DetailsSheet( private val delegate: Delegate, ) : Screen(context), ThemeDelegate { interface Delegate { - fun findInTable(codePoint: Int) + fun findInTable(codePoint: CodePoint) } private var charObj: CharObj? = null @@ -125,7 +125,7 @@ class DetailsSheet( vertical += actionCellHeight + 16.dp(context) onClick = { charObj?.let { value -> - delegate.findInTable(value.codePoint) + delegate.findInTable(CodePoint(value.codePointRaw)) } } } @@ -228,4 +228,4 @@ class DetailsSheet( themeManager.dividerPaint ) } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/vadiole/unicode/ui/table/CharRow.kt b/app/src/main/kotlin/vadiole/unicode/ui/table/CharRow.kt index bc809c4..2942b02 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/table/CharRow.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/table/CharRow.kt @@ -8,20 +8,21 @@ import android.view.HapticFeedbackConstants import android.view.MotionEvent import android.view.View import android.view.ViewConfiguration +import kotlin.math.floor import vadiole.unicode.UnicodeApp.Companion.themeManager import vadiole.unicode.data.CodePoint +import vadiole.unicode.data.CodePointArray import vadiole.unicode.ui.theme.ThemeDelegate import vadiole.unicode.ui.theme.key_windowSurfacePressed import vadiole.unicode.ui.theme.key_windowTextPrimary import vadiole.unicode.utils.extension.dp -import kotlin.math.floor class CharRow( context: Context, private val count: Int, private val delegate: Delegate ) : View(context), ThemeDelegate { - private var codePoints: Array = emptyArray() + private var codePoints: CodePointArray = CodePointArray(0) private val charPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { textAlign = Paint.Align.CENTER isSubpixelText = true @@ -58,7 +59,7 @@ class CharRow( isFocusable = true } - fun bind(codePoints: Array) { + fun bind(codePoints: CodePointArray) { this.codePoints = codePoints charRipples.fill(false) invalidate() @@ -160,4 +161,4 @@ class CharRow( fun onClick(codePoint: CodePoint) fun onLongClick(codePoint: CodePoint) } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/vadiole/unicode/ui/table/TableHelper.kt b/app/src/main/kotlin/vadiole/unicode/ui/table/TableHelper.kt index 5a7c56e..6d17e0b 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/table/TableHelper.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/table/TableHelper.kt @@ -3,14 +3,17 @@ package vadiole.unicode.ui.table import android.graphics.Paint import vadiole.unicode.data.Block import vadiole.unicode.data.CodePoint +import vadiole.unicode.data.CodePointArray import vadiole.unicode.data.UnicodeStorage import vadiole.unicode.data.config.UserConfig +import vadiole.unicode.data.filterMaybe +import vadiole.unicode.utils.extension.binarySearch import vadiole.unicode.utils.extension.worker class TableHelper(private val unicodeStorage: UnicodeStorage, private val userConfig: UserConfig) { var totalChars = UnicodeStorage.totalCharacters - var tableChars: List = emptyList() - var blocks: List = emptyList() + var tableChars: CodePointArray = CodePointArray(0) + var blocks: Array = emptyArray() private val glyphPaint = Paint() private var lastBlock: Block? = null private var lastBlockIndex = -1 @@ -19,7 +22,7 @@ class TableHelper(private val unicodeStorage: UnicodeStorage, private val userCo val count = if (fast) 256 else -1 var codePoints = unicodeStorage.getCodePoints(count) if (!userConfig.showUnsupportedChars) { - codePoints = codePoints.filter { glyphPaint.hasGlyph(it.char) } + codePoints = codePoints.filterMaybe { glyphPaint.hasGlyph(it.char) } } tableChars = codePoints if (!fast) { @@ -34,7 +37,8 @@ class TableHelper(private val unicodeStorage: UnicodeStorage, private val userCo fun getBlock(position: Int): Block? { if (position == 0) return null if (blocks.isEmpty()) return null - val codePoint = tableChars.getOrNull(position) ?: return null + if (position >= tableChars.size) return null + val codePoint = tableChars[position] val last = lastBlock if (last == null) { val index = blocks.binarySearch { block -> @@ -74,21 +78,18 @@ class TableHelper(private val unicodeStorage: UnicodeStorage, private val userCo fun getPosition(block: Block): Int { if (blocks.isEmpty()) return 0 - val index = tableChars.binarySearch { codePoint -> - when { - codePoint.value < block.start -> -1 - codePoint.value > block.start -> 1 - else -> 0 - } - } - if (index < 0) return 0 - return index + + return 0 } - fun getChars(position: Int, spanCount: Int): Array { - val codePoints = Array(spanCount) { index -> + fun getChars(position: Int, spanCount: Int): CodePointArray { + val codePoints = CodePointArray(spanCount) { index -> val globalIndex = position * spanCount + index - tableChars.getOrNull(globalIndex) ?: CodePoint(0) + if (globalIndex >= tableChars.size) { + CodePoint(0) + } else { + tableChars[globalIndex] + } } return codePoints } diff --git a/app/src/main/kotlin/vadiole/unicode/ui/table/TableScreen.kt b/app/src/main/kotlin/vadiole/unicode/ui/table/TableScreen.kt index 89daa84..f1a04e8 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/table/TableScreen.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/table/TableScreen.kt @@ -15,6 +15,7 @@ import kotlinx.coroutines.launch import vadiole.unicode.UnicodeApp.Companion.themeManager import vadiole.unicode.data.Block import vadiole.unicode.data.CodePoint +import vadiole.unicode.data.binarySearch import vadiole.unicode.ui.common.CollectionView import vadiole.unicode.ui.common.Screen import vadiole.unicode.ui.common.SearchBar @@ -186,14 +187,8 @@ class TableScreen( return false } - fun scrollToChar(codePoint: Int) { - val position = tableHelper.tableChars.binarySearch { codePointToCompare -> - when { - codePointToCompare.value > codePoint -> 1 - codePointToCompare.value < codePoint -> -1 - else -> 0 - } - } + fun scrollToChar(codePoint: CodePoint) { + val position = tableHelper.tableChars.binarySearch(codePoint) val row = position / spanCount val indexInRow = position % spanCount if (position >= 0) { diff --git a/app/src/main/kotlin/vadiole/unicode/ui/table/selector/BlockSelectorView.kt b/app/src/main/kotlin/vadiole/unicode/ui/table/selector/BlockSelectorView.kt index 033c7b1..7123175 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/table/selector/BlockSelectorView.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/table/selector/BlockSelectorView.kt @@ -26,7 +26,7 @@ import vadiole.unicode.utils.extension.dp class BlockSelectorView( context: Context, - private val blocks: List, + private val blocks: Array, private val delegate: Delegate, ) : CollectionView(context), ThemeDelegate { interface Delegate { diff --git a/app/src/main/kotlin/vadiole/unicode/utils/extension/Array.kt b/app/src/main/kotlin/vadiole/unicode/utils/extension/Array.kt new file mode 100644 index 0000000..50fcade --- /dev/null +++ b/app/src/main/kotlin/vadiole/unicode/utils/extension/Array.kt @@ -0,0 +1,30 @@ +package vadiole.unicode.utils.extension + +fun Array.binarySearch(fromIndex: Int = 0, toIndex: Int = size, comparison: (T) -> Int): Int { + rangeCheck(size, fromIndex, toIndex) + + var low = fromIndex + var high = toIndex - 1 + + while (low <= high) { + val mid = (low + high).ushr(1) // safe from overflows + val midVal = get(mid) + val cmp = comparison(midVal) + + if (cmp < 0) + low = mid + 1 + else if (cmp > 0) + high = mid - 1 + else + return mid // key found + } + return -(low + 1) // key not found +} + +private fun rangeCheck(size: Int, fromIndex: Int, toIndex: Int) { + when { + fromIndex > toIndex -> throw IllegalArgumentException("fromIndex ($fromIndex) is greater than toIndex ($toIndex).") + fromIndex < 0 -> throw IndexOutOfBoundsException("fromIndex ($fromIndex) is less than zero.") + toIndex > size -> throw IndexOutOfBoundsException("toIndex ($toIndex) is greater than size ($size).") + } +} From 125704afe1937ed299a0e8f0483b0043d8006ec6 Mon Sep 17 00:00:00 2001 From: vadiole Date: Sun, 2 Jul 2023 13:42:03 +0200 Subject: [PATCH 07/26] add float animator --- .../unicode/ui/common/FloatAnimator.kt | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 app/src/main/kotlin/vadiole/unicode/ui/common/FloatAnimator.kt diff --git a/app/src/main/kotlin/vadiole/unicode/ui/common/FloatAnimator.kt b/app/src/main/kotlin/vadiole/unicode/ui/common/FloatAnimator.kt new file mode 100644 index 0000000..cdcb524 --- /dev/null +++ b/app/src/main/kotlin/vadiole/unicode/ui/common/FloatAnimator.kt @@ -0,0 +1,32 @@ +package vadiole.unicode.ui.common + +import androidx.dynamicanimation.animation.FloatValueHolder +import androidx.dynamicanimation.animation.SpringAnimation +import androidx.dynamicanimation.animation.SpringForce + +class FloatAnimator( + initialValue: Float, + private val stiffness: Float = SpringForce.STIFFNESS_LOW, + private val dampingRatio: Float = SpringForce.DAMPING_RATIO_NO_BOUNCY, +) { + private val floatValueHolder = FloatValueHolder(initialValue) + private val springAnimation = SpringAnimation(floatValueHolder) + + fun onUpdate(callback: (value: Float) -> Unit): FloatAnimator { + springAnimation.addUpdateListener { _, value, _ -> + callback(value) + } + return this + } + + fun setValue(value: Float) { + springAnimation.apply { + cancel() + spring = SpringForce(value).apply { + this.stiffness = this@FloatAnimator.stiffness + this.dampingRatio = this@FloatAnimator.dampingRatio + } + start() + } + } +} From e85c49ea541e1152ee145bc33ff216f9cda54067 Mon Sep 17 00:00:00 2001 From: vadiole Date: Sun, 2 Jul 2023 14:39:38 +0200 Subject: [PATCH 08/26] add scrollbar drawable --- .../unicode/ui/common/ScrollbarDrawable.kt | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 app/src/main/kotlin/vadiole/unicode/ui/common/ScrollbarDrawable.kt diff --git a/app/src/main/kotlin/vadiole/unicode/ui/common/ScrollbarDrawable.kt b/app/src/main/kotlin/vadiole/unicode/ui/common/ScrollbarDrawable.kt new file mode 100644 index 0000000..a38c494 --- /dev/null +++ b/app/src/main/kotlin/vadiole/unicode/ui/common/ScrollbarDrawable.kt @@ -0,0 +1,110 @@ +package vadiole.unicode.ui.common + +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.ColorFilter +import android.graphics.Paint +import android.graphics.PixelFormat.OPAQUE +import android.graphics.Rect +import android.graphics.RectF +import android.graphics.drawable.Drawable +import android.os.Build +import kotlin.math.roundToInt + +class ScrollbarDrawable : Drawable() { + + private val scrollBarPaint = Paint().apply { + isAntiAlias = true + color = Color.GRAY + } + private var range = 0 + private var offset = 0 + private var extent = 0 + private var thumbRectCache = RectF() + private var changed = false + private var rangeChanged = false + + fun setParameters(range: Int, offset: Int, extent: Int) { + if (this.range != range || this.offset != offset || this.extent != extent) { + rangeChanged = true + } + this.range = range + this.offset = offset + this.extent = extent + } + + fun setColor(color: Int) { + scrollBarPaint.color = color + } + + override fun onBoundsChange(bounds: Rect) { + super.onBoundsChange(bounds) + changed = true + } + + override fun draw(canvas: Canvas) { + val extent: Int = extent + val range: Int = range + var drawThumb = true + if (extent <= 0 || range <= extent) { + drawThumb = false + } + val drawableBounds = bounds + if ( + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + canvas.quickReject( + drawableBounds.left.toFloat(), + drawableBounds.top.toFloat(), + drawableBounds.right.toFloat(), + drawableBounds.bottom.toFloat(), + ) + } else { + @Suppress("DEPRECATION") + canvas.quickReject( + drawableBounds.left.toFloat(), + drawableBounds.top.toFloat(), + drawableBounds.right.toFloat(), + drawableBounds.bottom.toFloat(), + Canvas.EdgeType.AA, + ) + } + ) { + return + } + + if (drawThumb) { + val trackHeight = drawableBounds.height() + val thickness = drawableBounds.width() + + val minThumbHeight = thickness * 10 + val thumbHeight = (trackHeight.toFloat() * extent / range).roundToInt().coerceAtLeast(minThumbHeight) + val thumbTop = ((trackHeight - thumbHeight).toFloat() * offset / (range - extent)).roundToInt() + + drawThumb(canvas, drawableBounds, thumbTop, thumbHeight) + } + } + + private fun drawThumb(canvas: Canvas, bounds: Rect, offset: Int, length: Int) { + val thumbRect: RectF = thumbRectCache + val changed = rangeChanged || changed + if (changed) { + thumbRect.set(bounds.left.toFloat(), (bounds.top + offset).toFloat(), bounds.right.toFloat(), (bounds.top + offset + length).toFloat()) + } + canvas.drawRoundRect(thumbRect, bounds.width().toFloat(), bounds.width().toFloat(), scrollBarPaint) + } + + override fun setAlpha(alpha: Int) { + scrollBarPaint.alpha = alpha + } + + override fun setColorFilter(colorFilter: ColorFilter?) { + scrollBarPaint.colorFilter = colorFilter + } + + @Suppress("OVERRIDE_DEPRECATION") + override fun getOpacity(): Int = OPAQUE + + override fun toString(): String { + return "ScrollBarDrawable: range=$range offset=$offset extent=$extent" + } +} From cf338a191dbc6e88830999a05f11a92d6e3bb49e Mon Sep 17 00:00:00 2001 From: vadiole Date: Sun, 2 Jul 2023 14:44:06 +0200 Subject: [PATCH 09/26] add VerticalScrollBarItemDecoration --- .../{FloatAnimator.kt => SpringAnimator.kt} | 8 +- .../common/VerticalScrollBarItemDecoration.kt | 82 +++++++++++++++++++ 2 files changed, 86 insertions(+), 4 deletions(-) rename app/src/main/kotlin/vadiole/unicode/ui/common/{FloatAnimator.kt => SpringAnimator.kt} (78%) create mode 100644 app/src/main/kotlin/vadiole/unicode/ui/common/VerticalScrollBarItemDecoration.kt diff --git a/app/src/main/kotlin/vadiole/unicode/ui/common/FloatAnimator.kt b/app/src/main/kotlin/vadiole/unicode/ui/common/SpringAnimator.kt similarity index 78% rename from app/src/main/kotlin/vadiole/unicode/ui/common/FloatAnimator.kt rename to app/src/main/kotlin/vadiole/unicode/ui/common/SpringAnimator.kt index cdcb524..b5f9111 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/common/FloatAnimator.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/common/SpringAnimator.kt @@ -4,7 +4,7 @@ import androidx.dynamicanimation.animation.FloatValueHolder import androidx.dynamicanimation.animation.SpringAnimation import androidx.dynamicanimation.animation.SpringForce -class FloatAnimator( +class SpringAnimator( initialValue: Float, private val stiffness: Float = SpringForce.STIFFNESS_LOW, private val dampingRatio: Float = SpringForce.DAMPING_RATIO_NO_BOUNCY, @@ -12,7 +12,7 @@ class FloatAnimator( private val floatValueHolder = FloatValueHolder(initialValue) private val springAnimation = SpringAnimation(floatValueHolder) - fun onUpdate(callback: (value: Float) -> Unit): FloatAnimator { + fun onUpdate(callback: (value: Float) -> Unit): SpringAnimator { springAnimation.addUpdateListener { _, value, _ -> callback(value) } @@ -23,8 +23,8 @@ class FloatAnimator( springAnimation.apply { cancel() spring = SpringForce(value).apply { - this.stiffness = this@FloatAnimator.stiffness - this.dampingRatio = this@FloatAnimator.dampingRatio + this.stiffness = this@SpringAnimator.stiffness + this.dampingRatio = this@SpringAnimator.dampingRatio } start() } diff --git a/app/src/main/kotlin/vadiole/unicode/ui/common/VerticalScrollBarItemDecoration.kt b/app/src/main/kotlin/vadiole/unicode/ui/common/VerticalScrollBarItemDecoration.kt new file mode 100644 index 0000000..e2e3129 --- /dev/null +++ b/app/src/main/kotlin/vadiole/unicode/ui/common/VerticalScrollBarItemDecoration.kt @@ -0,0 +1,82 @@ +package vadiole.unicode.ui.common + +import android.graphics.Canvas +import androidx.dynamicanimation.animation.SpringForce +import androidx.recyclerview.widget.RecyclerView + +class VerticalScrollBarItemDecoration( + private val recyclerView: RecyclerView, + private val scrollbarDrawable: ScrollbarDrawable, + private val scrollBarWidth: Int, +) : RecyclerView.ItemDecoration() { + + enum class State(val alpha: Float) { + HIDDEN(0f), + VISIBLE(1f), + } + + private var recyclerViewWidth = 0 + private var recyclerViewHeight = 0 + private val stateAnimator = SpringAnimator(initialValue = 0f, stiffness = SpringForce.STIFFNESS_VERY_LOW) + .onUpdate { value -> + scrollbarDrawable.alpha = (value * 255).toInt() + requestRedraw() + } + private val hideRunnable = Runnable { state = State.HIDDEN } + private var state = State.HIDDEN + set(value) { + if (field == value) return + field = value + stateAnimator.setValue(value.alpha) + requestRedraw() + if (value == State.VISIBLE) { + recyclerView.removeCallbacks(hideRunnable) + recyclerView.postDelayed(hideRunnable, 1500) + } + } + private val mOnScrollListener: RecyclerView.OnScrollListener = object : RecyclerView.OnScrollListener() { + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + updateScrollPosition() + } + } + + init { + recyclerView.addOnScrollListener(mOnScrollListener) + } + + private fun updateScrollPosition() { + val range = recyclerView.computeVerticalScrollRange() + val offset = recyclerView.computeVerticalScrollOffset() + val extent = recyclerView.computeVerticalScrollExtent() + + scrollbarDrawable.setParameters(range, offset, extent) + state = State.VISIBLE + } + + private fun requestRedraw() { + recyclerView.invalidate() + } + + override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { + if (recyclerViewWidth != recyclerView.width + || recyclerViewHeight != recyclerView.height + ) { + recyclerViewWidth = recyclerView.width + recyclerViewHeight = recyclerView.height + // This is due to the different events ordering when keyboard is opened or + // retracted vs rotate. Hence to avoid corner cases we just disable the + // scroller when size changed, and wait until the scroll position is recomputed + // before showing it back. + this.state = State.HIDDEN + return + } + + scrollbarDrawable.setBounds( + recyclerViewWidth - scrollBarWidth, + recyclerView.paddingTop, + recyclerViewWidth, + recyclerViewHeight - recyclerView.paddingBottom + ) + scrollbarDrawable.draw(c) + } +} From 4f6e2f9291249cdaa542bc0cfd0631fb5ad5c1f7 Mon Sep 17 00:00:00 2001 From: vadiole Date: Sun, 2 Jul 2023 15:01:32 +0200 Subject: [PATCH 10/26] add scrollbar to block selector view --- .../ui/table/selector/BlockSelectorView.kt | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/vadiole/unicode/ui/table/selector/BlockSelectorView.kt b/app/src/main/kotlin/vadiole/unicode/ui/table/selector/BlockSelectorView.kt index 7123175..3853f3b 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/table/selector/BlockSelectorView.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/table/selector/BlockSelectorView.kt @@ -19,9 +19,12 @@ import vadiole.unicode.UnicodeApp.Companion.themeManager import vadiole.unicode.data.Block import vadiole.unicode.ui.common.CollectionItemDecoration import vadiole.unicode.ui.common.CollectionView +import vadiole.unicode.ui.common.ScrollbarDrawable import vadiole.unicode.ui.common.Squircle +import vadiole.unicode.ui.common.VerticalScrollBarItemDecoration import vadiole.unicode.ui.theme.ThemeDelegate import vadiole.unicode.ui.theme.key_dialogSurface +import vadiole.unicode.ui.theme.key_dialogSurfacePressed import vadiole.unicode.utils.extension.dp class BlockSelectorView( @@ -34,7 +37,7 @@ class BlockSelectorView( } private val squircle = Squircle().apply { - cornerRadiusPx = 12.dp(context) + cornerRadiusPx = 8.dp(context) } private val backgroundDrawable = ColorDrawable() @@ -61,7 +64,13 @@ class BlockSelectorView( } } - private val itemDecoration = CollectionItemDecoration(leftPadding = 14f.dp(context)) + private val scrollbarDrawable = ScrollbarDrawable() + private val scrollBars = VerticalScrollBarItemDecoration( + recyclerView = this, + scrollbarDrawable = scrollbarDrawable, + scrollBarWidth = 4.dp(context), + ) + private val divider = CollectionItemDecoration(leftPadding = 14f.dp(context)) private var showAnimator: ValueAnimator? = null private val anchorDrawable = context.getDrawable(R.drawable.ic_anchor)!!.mutate() private val topMargin = anchorDrawable.intrinsicHeight - 4.dp(context) @@ -73,7 +82,8 @@ class BlockSelectorView( layoutManager = blockSelectorLayoutManager setItemViewCacheSize(8) setAdapter(adapter) - addItemDecoration(itemDecoration) + addItemDecoration(divider) + addItemDecoration(scrollBars) squircle.attach(this) setPadding(0, 8.dp(context) + topMargin, 0, 8.dp(context)) layoutAnimation = LayoutAnimationController( @@ -141,6 +151,7 @@ class BlockSelectorView( override fun applyTheme() { backgroundDrawable.color = themeManager.getColor(key_dialogSurface) anchorDrawable.setTint(themeManager.getColor(key_dialogSurface)) + scrollbarDrawable.setColor(themeManager.getColor(key_dialogSurfacePressed)) invalidate() } From d215ad1696ca7533fd9e1bc95cc5024523823ed7 Mon Sep 17 00:00:00 2001 From: vadiole Date: Sun, 2 Jul 2023 15:44:55 +0200 Subject: [PATCH 11/26] add scrollbar to table view --- .../unicode/ui/common/CollectionView.kt | 1 - .../vadiole/unicode/ui/table/TableView.kt | 21 +++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/vadiole/unicode/ui/common/CollectionView.kt b/app/src/main/kotlin/vadiole/unicode/ui/common/CollectionView.kt index 46cedef..0d6cee6 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/common/CollectionView.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/common/CollectionView.kt @@ -7,7 +7,6 @@ import androidx.recyclerview.widget.RecyclerView open class CollectionView(context: Context) : RecyclerView(context) { var isScrollEnabled = true - var fastScrollAnimationRunning = false init { overScrollMode = View.OVER_SCROLL_ALWAYS diff --git a/app/src/main/kotlin/vadiole/unicode/ui/table/TableView.kt b/app/src/main/kotlin/vadiole/unicode/ui/table/TableView.kt index a53598c..e10ee84 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/table/TableView.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/table/TableView.kt @@ -3,7 +3,12 @@ package vadiole.unicode.ui.table import android.content.Context import androidx.core.view.doOnNextLayout import androidx.recyclerview.widget.LinearLayoutManager +import vadiole.unicode.UnicodeApp.Companion.themeManager import vadiole.unicode.ui.common.CollectionView +import vadiole.unicode.ui.common.ScrollbarDrawable +import vadiole.unicode.ui.common.VerticalScrollBarItemDecoration +import vadiole.unicode.ui.theme.ThemeDelegate +import vadiole.unicode.ui.theme.key_dialogSurfacePressed import vadiole.unicode.utils.extension.dp import vadiole.unicode.utils.extension.setPaddingHorizontal @@ -12,9 +17,15 @@ class TableView( private val adapter: TableAdapter, private val spanCount: Int, private val delegate: Delegate, -) : CollectionView(context) { +) : CollectionView(context), ThemeDelegate { private val tableLayoutManager = LinearLayoutManager(context) private val itemDecoration = TableItemDecoration() + private val scrollbarDrawable = ScrollbarDrawable() + private val scrollBarItemDecoration = VerticalScrollBarItemDecoration( + recyclerView = this, + scrollbarDrawable = scrollbarDrawable, + scrollBarWidth = 4.dp(context), + ) init { recycledViewPool.setMaxRecycledViews(0, spanCount * 6) @@ -22,7 +33,9 @@ class TableView( setItemViewCacheSize(spanCount * 2) setAdapter(adapter) addItemDecoration(itemDecoration) + addItemDecoration(scrollBarItemDecoration) setPaddingHorizontal(8.dp(context)) + themeManager.observe(this) } override fun onScrolled(dx: Int, dy: Int) { @@ -43,4 +56,8 @@ class TableView( interface Delegate { fun onBlockChanged(name: String?) } -} \ No newline at end of file + + override fun applyTheme() { + scrollbarDrawable.setColor(themeManager.getColor(key_dialogSurfacePressed)) + } +} From 6cb38dc964c4d5a6d1c4599324fca24c719acd31 Mon Sep 17 00:00:00 2001 From: vadiole Date: Sun, 2 Jul 2023 15:56:34 +0200 Subject: [PATCH 12/26] add scrollbar to search view --- .../vadiole/unicode/ui/table/TableView.kt | 1 + .../ui/table/search/SearchResultView.kt | 21 +++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/vadiole/unicode/ui/table/TableView.kt b/app/src/main/kotlin/vadiole/unicode/ui/table/TableView.kt index e10ee84..529f697 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/table/TableView.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/table/TableView.kt @@ -59,5 +59,6 @@ class TableView( override fun applyTheme() { scrollbarDrawable.setColor(themeManager.getColor(key_dialogSurfacePressed)) + invalidate() } } diff --git a/app/src/main/kotlin/vadiole/unicode/ui/table/search/SearchResultView.kt b/app/src/main/kotlin/vadiole/unicode/ui/table/search/SearchResultView.kt index 71dfd7f..24110b8 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/table/search/SearchResultView.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/table/search/SearchResultView.kt @@ -2,22 +2,32 @@ package vadiole.unicode.ui.table.search import android.content.Context import androidx.recyclerview.widget.LinearLayoutManager +import vadiole.unicode.UnicodeApp.Companion.themeManager import vadiole.unicode.ui.common.CollectionItemDecoration import vadiole.unicode.ui.common.CollectionView +import vadiole.unicode.ui.common.ScrollbarDrawable +import vadiole.unicode.ui.common.VerticalScrollBarItemDecoration +import vadiole.unicode.ui.theme.ThemeDelegate +import vadiole.unicode.ui.theme.key_dialogSurfacePressed import vadiole.unicode.utils.extension.dp class SearchResultView( context: Context, adapter: Adapter, private val delegate: Delegate, -) : CollectionView(context) { +) : CollectionView(context), ThemeDelegate { interface Delegate { fun onStartScrolling() } private val searchLayoutManager = LinearLayoutManager(context) - + private val scrollbarDrawable = ScrollbarDrawable() + private val scrollBars = VerticalScrollBarItemDecoration( + recyclerView = this, + scrollbarDrawable = scrollbarDrawable, + scrollBarWidth = 4.dp(context), + ) private val itemDecoration = CollectionItemDecoration(leftPadding = 14f.dp(context)) init { @@ -26,6 +36,8 @@ class SearchResultView( setItemViewCacheSize(8) setAdapter(adapter) addItemDecoration(itemDecoration) + addItemDecoration(scrollBars) + themeManager.observe(this) } override fun onScrollStateChanged(state: Int) { @@ -33,4 +45,9 @@ class SearchResultView( delegate.onStartScrolling() } } + + override fun applyTheme() { + scrollbarDrawable.setColor(themeManager.getColor(key_dialogSurfacePressed)) + invalidate() + } } From 1cbfddd5053d8d6da02b61f3aede036390f7f3da Mon Sep 17 00:00:00 2001 From: vadiole Date: Mon, 3 Jul 2023 07:40:44 +0200 Subject: [PATCH 13/26] add scroll to selected block --- app/build.gradle.kts | 4 +-- .../vadiole/unicode/ui/table/TableHelper.kt | 29 ++++++++++++++++++- .../vadiole/unicode/ui/table/TableScreen.kt | 2 +- .../vadiole/unicode/ui/table/TableView.kt | 4 +++ 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 310314f..c7830af 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -10,8 +10,8 @@ android { applicationId = "vadiole.unicode" minSdk = 26 targetSdk = 33 - versionCode = 131 - versionName = "1.3.1" + versionCode = 132 + versionName = "1.3.2" resourceConfigurations.addAll(listOf("en")) setProperty("archivesBaseName", "unicode-v$versionName") } diff --git a/app/src/main/kotlin/vadiole/unicode/ui/table/TableHelper.kt b/app/src/main/kotlin/vadiole/unicode/ui/table/TableHelper.kt index 6d17e0b..c78522b 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/table/TableHelper.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/table/TableHelper.kt @@ -77,9 +77,36 @@ class TableHelper(private val unicodeStorage: UnicodeStorage, private val userCo } fun getPosition(block: Block): Int { + if (tableChars.isEmpty()) return 0 if (blocks.isEmpty()) return 0 + if (block.start == 0) return 0 - return 0 + var low = 0 + var high = tableChars.size - 1 + var position = 0 + + while (low <= high) { + val mid = (low + high).ushr(1) // safe from overflows + val midVal = tableChars[mid] + + val cmp = block.contains(midVal) + when { + cmp > 0 -> { + low = mid + 1 + } + + cmp < 0 -> { + high = mid - 1 + } + + else -> { + position = mid + high = mid - 1 + } + } + } + + return position } fun getChars(position: Int, spanCount: Int): CodePointArray { diff --git a/app/src/main/kotlin/vadiole/unicode/ui/table/TableScreen.kt b/app/src/main/kotlin/vadiole/unicode/ui/table/TableScreen.kt index f1a04e8..3b341c3 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/table/TableScreen.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/table/TableScreen.kt @@ -51,7 +51,7 @@ class TableScreen( private val blockSelectorDelegate = object : BlockSelectorView.Delegate { override fun onBlockSelected(block: Block) { popup?.dismiss() - tableView.scrollToPosition(tableHelper.getPosition(block) / spanCount) + tableView.scrollToPositionTop((tableHelper.getPosition(block) / spanCount) + 1) } } private val charCellDelegate = object : CharRow.Delegate { diff --git a/app/src/main/kotlin/vadiole/unicode/ui/table/TableView.kt b/app/src/main/kotlin/vadiole/unicode/ui/table/TableView.kt index 529f697..350255b 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/table/TableView.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/table/TableView.kt @@ -53,6 +53,10 @@ class TableView( } } + fun scrollToPositionTop(row: Int) { + tableLayoutManager.scrollToPositionWithOffset(row, 0) + } + interface Delegate { fun onBlockChanged(name: String?) } From 10f3b368219d63174b95101baf43a0377fd6ac08 Mon Sep 17 00:00:00 2001 From: vadiole Date: Mon, 3 Jul 2023 09:44:00 +0200 Subject: [PATCH 14/26] fix search --- .../kotlin/vadiole/unicode/ui/table/search/SearchHelper.kt | 7 ++++++- .../main/kotlin/vadiole/unicode/utils/extension/Array.kt | 4 ++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/vadiole/unicode/ui/table/search/SearchHelper.kt b/app/src/main/kotlin/vadiole/unicode/ui/table/search/SearchHelper.kt index 52d4876..ddf13e9 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/table/search/SearchHelper.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/table/search/SearchHelper.kt @@ -1,15 +1,20 @@ package vadiole.unicode.ui.table.search +import android.graphics.Paint import vadiole.unicode.data.SearchResult import vadiole.unicode.data.UnicodeStorage +import vadiole.unicode.utils.extension.filterMaybe class SearchHelper(private val unicodeStorage: UnicodeStorage) { + private val glyphPaint = Paint() var searchResult: Array = emptyArray() suspend fun search(query: String) { searchResult = if (query.isBlank()) { emptyArray() } else { unicodeStorage.findCharsByName(query) + .filterMaybe { glyphPaint.hasGlyph(it.codePoint.char) } + .toTypedArray() } } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/vadiole/unicode/utils/extension/Array.kt b/app/src/main/kotlin/vadiole/unicode/utils/extension/Array.kt index 50fcade..b83a9ba 100644 --- a/app/src/main/kotlin/vadiole/unicode/utils/extension/Array.kt +++ b/app/src/main/kotlin/vadiole/unicode/utils/extension/Array.kt @@ -28,3 +28,7 @@ private fun rangeCheck(size: Int, fromIndex: Int, toIndex: Int) { toIndex > size -> throw IndexOutOfBoundsException("toIndex ($toIndex) is greater than size ($size).") } } + +inline fun Array.filterMaybe(predicate: (T) -> Boolean): List { + return filterTo(ArrayList(size), predicate) +} From 70881d2b109fc50c4c03ecdd5642d7d5c02bc14f Mon Sep 17 00:00:00 2001 From: vadiole Date: Mon, 3 Jul 2023 10:02:23 +0200 Subject: [PATCH 15/26] fix search performance --- .../kotlin/vadiole/unicode/data/UnicodeStorage.kt | 15 ++++++++++----- .../vadiole/unicode/ui/table/TableScreen.kt | 4 +++- .../unicode/ui/table/search/SearchHelper.kt | 4 ++-- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/app/src/main/kotlin/vadiole/unicode/data/UnicodeStorage.kt b/app/src/main/kotlin/vadiole/unicode/data/UnicodeStorage.kt index bbe6dae..40c88de 100644 --- a/app/src/main/kotlin/vadiole/unicode/data/UnicodeStorage.kt +++ b/app/src/main/kotlin/vadiole/unicode/data/UnicodeStorage.kt @@ -100,21 +100,26 @@ class UnicodeStorage(private val context: Context) { return@io result } - suspend fun findCharsByName(input: String): Array = io { + suspend fun findCharsByName(input: String, count: Int): Array = io { + val query = if (count > 0) { + "$queryFindChars LIMIT $count" + } else { + queryFindChars + } val args = arrayOf("%$input%", input, "$input%") - openDatabase().rawQuery(queryFindChars, args).use { cursor -> + val result: Array + openDatabase().rawQuery(query, args).use { cursor -> val rowsCount = cursor.count val codePointIndex = cursor.getColumnIndex("code_point") val nameIndex = cursor.getColumnIndex("name") - val result = Array(rowsCount) { + result = Array(rowsCount) { cursor.moveToPosition(it) val codePoint = cursor.getInt(codePointIndex) val name = cursor.getString(nameIndex) SearchResult(CodePoint(codePoint), name) } - return@io result } - + return@io result } companion object { diff --git a/app/src/main/kotlin/vadiole/unicode/ui/table/TableScreen.kt b/app/src/main/kotlin/vadiole/unicode/ui/table/TableScreen.kt index 3b341c3..9e5f9d7 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/table/TableScreen.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/table/TableScreen.kt @@ -123,9 +123,11 @@ class TableScreen( override fun onTextChanged(string: String) { searchJob?.cancel() searchJob = launch { - searchHelper.search(string) + searchHelper.search(string, 64) searchResultView.scrollToPosition(0) searchAdapter.notifyDataSetChanged() + searchHelper.search(string) + searchAdapter.notifyDataSetChanged() } } diff --git a/app/src/main/kotlin/vadiole/unicode/ui/table/search/SearchHelper.kt b/app/src/main/kotlin/vadiole/unicode/ui/table/search/SearchHelper.kt index ddf13e9..e8dc0e1 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/table/search/SearchHelper.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/table/search/SearchHelper.kt @@ -8,11 +8,11 @@ import vadiole.unicode.utils.extension.filterMaybe class SearchHelper(private val unicodeStorage: UnicodeStorage) { private val glyphPaint = Paint() var searchResult: Array = emptyArray() - suspend fun search(query: String) { + suspend fun search(query: String, count: Int = -1) { searchResult = if (query.isBlank()) { emptyArray() } else { - unicodeStorage.findCharsByName(query) + unicodeStorage.findCharsByName(query, count) .filterMaybe { glyphPaint.hasGlyph(it.codePoint.char) } .toTypedArray() } From 7cf533e2ab48ee96484355cd0f215bb9e8f566de Mon Sep 17 00:00:00 2001 From: vadiole Date: Wed, 5 Jul 2023 23:31:33 +0200 Subject: [PATCH 16/26] fix search query --- .../kotlin/vadiole/unicode/data/UnicodeStorage.kt | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/vadiole/unicode/data/UnicodeStorage.kt b/app/src/main/kotlin/vadiole/unicode/data/UnicodeStorage.kt index 40c88de..da401f3 100644 --- a/app/src/main/kotlin/vadiole/unicode/data/UnicodeStorage.kt +++ b/app/src/main/kotlin/vadiole/unicode/data/UnicodeStorage.kt @@ -106,7 +106,13 @@ class UnicodeStorage(private val context: Context) { } else { queryFindChars } - val args = arrayOf("%$input%", input, "$input%") + val args = arrayOf( + "% $input", + "$input %", + "% $input %", + "%$input%", + input, "% $input", "$input %", "% $input %", + ) val result: Array openDatabase().rawQuery(query, args).use { cursor -> val rowsCount = cursor.count @@ -131,7 +137,10 @@ class UnicodeStorage(private val context: Context) { private const val queryGetBlocks = "SELECT id, `end`, name FROM block" private const val queryFindChars = "SELECT id, code_point, name " + "FROM char " + - "WHERE name LIKE ?" + - "ORDER BY (CASE WHEN name = ? THEN 1 WHEN name LIKE ? THEN 2 ELSE 3 END), name" + "WHERE name LIKE ? " + + "OR name LIKE ? " + + "OR name Like ? " + + "OR name LIKE ? " + + "ORDER BY (CASE WHEN name = ? THEN 1 WHEN name LIKE ? THEN 2 WHEN name LIKE ? THEN 3 WHEN name = ? THEN 4 ELSE 5 END), id" } } From bc434232a920e38dd5667439aa9d43fcb4d9ab3d Mon Sep 17 00:00:00 2001 From: vadiole Date: Thu, 6 Jul 2023 00:00:32 +0200 Subject: [PATCH 17/26] fix empty blocks --- app/src/main/kotlin/vadiole/unicode/ui/table/TableHelper.kt | 4 ++++ app/src/main/kotlin/vadiole/unicode/ui/table/TableScreen.kt | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/vadiole/unicode/ui/table/TableHelper.kt b/app/src/main/kotlin/vadiole/unicode/ui/table/TableHelper.kt index c78522b..aac9b7c 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/table/TableHelper.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/table/TableHelper.kt @@ -8,6 +8,7 @@ import vadiole.unicode.data.UnicodeStorage import vadiole.unicode.data.config.UserConfig import vadiole.unicode.data.filterMaybe import vadiole.unicode.utils.extension.binarySearch +import vadiole.unicode.utils.extension.filterMaybe import vadiole.unicode.utils.extension.worker class TableHelper(private val unicodeStorage: UnicodeStorage, private val userConfig: UserConfig) { @@ -32,6 +33,9 @@ class TableHelper(private val unicodeStorage: UnicodeStorage, private val userCo suspend fun loadBlocks() = worker { blocks = unicodeStorage.getBlocks() + blocks = blocks.filterMaybe { block -> + getPosition(block) != 0 || block.start == 0 + }.toTypedArray() } fun getBlock(position: Int): Block? { diff --git a/app/src/main/kotlin/vadiole/unicode/ui/table/TableScreen.kt b/app/src/main/kotlin/vadiole/unicode/ui/table/TableScreen.kt index 9e5f9d7..fa30aed 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/table/TableScreen.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/table/TableScreen.kt @@ -175,7 +175,6 @@ class TableScreen( tableHelper.loadChars(fast = false) tableAdapter.notifyDataSetChanged() tableHelper.loadBlocks() - tableAdapter.notifyDataSetChanged() } } From b249ae8ab196f1cbaab405d7a8e2490912ed636a Mon Sep 17 00:00:00 2001 From: vadiole Date: Fri, 14 Jul 2023 17:00:10 +0200 Subject: [PATCH 18/26] fix search query --- .../vadiole/unicode/data/UnicodeStorage.kt | 42 +++++++++++-------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/app/src/main/kotlin/vadiole/unicode/data/UnicodeStorage.kt b/app/src/main/kotlin/vadiole/unicode/data/UnicodeStorage.kt index da401f3..dd0af50 100644 --- a/app/src/main/kotlin/vadiole/unicode/data/UnicodeStorage.kt +++ b/app/src/main/kotlin/vadiole/unicode/data/UnicodeStorage.kt @@ -4,7 +4,9 @@ import android.content.Context import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase.OPEN_READONLY import android.database.sqlite.SQLiteDatabase.openDatabase +import android.util.Log import java.io.File +import kotlin.system.measureTimeMillis import vadiole.unicode.utils.extension.io class UnicodeStorage(private val context: Context) { @@ -107,23 +109,24 @@ class UnicodeStorage(private val context: Context) { queryFindChars } val args = arrayOf( - "% $input", - "$input %", - "% $input %", "%$input%", - input, "% $input", "$input %", "% $input %", + input, "% $input", "$input %", "$input%", "% $input %", ) val result: Array - openDatabase().rawQuery(query, args).use { cursor -> - val rowsCount = cursor.count - val codePointIndex = cursor.getColumnIndex("code_point") - val nameIndex = cursor.getColumnIndex("name") - result = Array(rowsCount) { - cursor.moveToPosition(it) - val codePoint = cursor.getInt(codePointIndex) - val name = cursor.getString(nameIndex) - SearchResult(CodePoint(codePoint), name) + measureTimeMillis { + openDatabase().rawQuery(query, args).use { cursor -> + val rowsCount = cursor.count + val codePointIndex = cursor.getColumnIndex("code_point") + val nameIndex = cursor.getColumnIndex("name") + result = Array(rowsCount) { + cursor.moveToPosition(it) + val codePoint = cursor.getInt(codePointIndex) + val name = cursor.getString(nameIndex) + SearchResult(CodePoint(codePoint), name) + } } + }.also { + Log.d("UnicodeStorage", "perf $it") } return@io result } @@ -138,9 +141,14 @@ class UnicodeStorage(private val context: Context) { private const val queryFindChars = "SELECT id, code_point, name " + "FROM char " + "WHERE name LIKE ? " + - "OR name LIKE ? " + - "OR name Like ? " + - "OR name LIKE ? " + - "ORDER BY (CASE WHEN name = ? THEN 1 WHEN name LIKE ? THEN 2 WHEN name LIKE ? THEN 3 WHEN name = ? THEN 4 ELSE 5 END), id" + "ORDER BY (" + + "CASE " + + "WHEN name = ? THEN 1 " + + "WHEN name LIKE ? THEN 2 " + + "WHEN name LIKE ? THEN 4 " + + "WHEN name LIKE ? THEN 3 " + + "WHEN name LIKE ? THEN 5 " + + "ELSE 6 END), " + + "id" } } From 94fee6c862d9456bd233842a30d0344b8e4a491d Mon Sep 17 00:00:00 2001 From: vadiole Date: Fri, 14 Jul 2023 19:11:08 +0200 Subject: [PATCH 19/26] fix ScrollbarDrawable min height --- .../kotlin/vadiole/unicode/ui/common/ScrollbarDrawable.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/vadiole/unicode/ui/common/ScrollbarDrawable.kt b/app/src/main/kotlin/vadiole/unicode/ui/common/ScrollbarDrawable.kt index a38c494..33adf1d 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/common/ScrollbarDrawable.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/common/ScrollbarDrawable.kt @@ -76,8 +76,9 @@ class ScrollbarDrawable : Drawable() { val trackHeight = drawableBounds.height() val thickness = drawableBounds.width() - val minThumbHeight = thickness * 10 - val thumbHeight = (trackHeight.toFloat() * extent / range).roundToInt().coerceAtLeast(minThumbHeight) + val minThumbHeight = thickness * 8 + val ratio = extent.toFloat() / range + val thumbHeight = ((trackHeight - minThumbHeight) * ratio + minThumbHeight).roundToInt() val thumbTop = ((trackHeight - thumbHeight).toFloat() * offset / (range - extent)).roundToInt() drawThumb(canvas, drawableBounds, thumbTop, thumbHeight) From 1110e1caad85c81c9e232454c46ab4bc6d347623 Mon Sep 17 00:00:00 2001 From: vadiole Date: Fri, 14 Jul 2023 19:11:30 +0200 Subject: [PATCH 20/26] stop search result scroll when update --- app/src/main/kotlin/vadiole/unicode/ui/table/TableScreen.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/kotlin/vadiole/unicode/ui/table/TableScreen.kt b/app/src/main/kotlin/vadiole/unicode/ui/table/TableScreen.kt index fa30aed..befeec4 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/table/TableScreen.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/table/TableScreen.kt @@ -123,6 +123,7 @@ class TableScreen( override fun onTextChanged(string: String) { searchJob?.cancel() searchJob = launch { + searchResultView.stopScroll() searchHelper.search(string, 64) searchResultView.scrollToPosition(0) searchAdapter.notifyDataSetChanged() From dde05ad1bc223ffb6928b48adcc861f1f245f913 Mon Sep 17 00:00:00 2001 From: vadiole Date: Fri, 14 Jul 2023 19:11:53 +0200 Subject: [PATCH 21/26] add hide scrollbar when data changes --- .../common/VerticalScrollBarItemDecoration.kt | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/vadiole/unicode/ui/common/VerticalScrollBarItemDecoration.kt b/app/src/main/kotlin/vadiole/unicode/ui/common/VerticalScrollBarItemDecoration.kt index e2e3129..92076ca 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/common/VerticalScrollBarItemDecoration.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/common/VerticalScrollBarItemDecoration.kt @@ -1,6 +1,7 @@ package vadiole.unicode.ui.common import android.graphics.Canvas +import androidx.core.view.doOnPreDraw import androidx.dynamicanimation.animation.SpringForce import androidx.recyclerview.widget.RecyclerView @@ -34,14 +35,29 @@ class VerticalScrollBarItemDecoration( recyclerView.postDelayed(hideRunnable, 1500) } } - private val mOnScrollListener: RecyclerView.OnScrollListener = object : RecyclerView.OnScrollListener() { + private val onScrollListener: RecyclerView.OnScrollListener = object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + if (dx == 0 && dy == 0) { + state = State.HIDDEN + return + } updateScrollPosition() } } + private val dataObserver = object : RecyclerView.AdapterDataObserver() { + override fun onChanged() { + if (recyclerView.scrollState == RecyclerView.SCROLL_STATE_IDLE) { + state = State.HIDDEN + } + } + } + init { - recyclerView.addOnScrollListener(mOnScrollListener) + recyclerView.addOnScrollListener(onScrollListener) + recyclerView.doOnPreDraw { + recyclerView.adapter?.registerAdapterDataObserver(dataObserver) + } } private fun updateScrollPosition() { From eaed1bd6b7364f1cc5f4a51528a86b9031b23ff7 Mon Sep 17 00:00:00 2001 From: vadiole Date: Sat, 15 Jul 2023 10:43:10 +0200 Subject: [PATCH 22/26] fix search result view clipping --- .../main/kotlin/vadiole/unicode/ui/table/TableScreen.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/vadiole/unicode/ui/table/TableScreen.kt b/app/src/main/kotlin/vadiole/unicode/ui/table/TableScreen.kt index befeec4..9519655 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/table/TableScreen.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/table/TableScreen.kt @@ -155,6 +155,8 @@ class TableScreen( } init { + clipChildren = true + clipToPadding = false themeManager.observe(this) setWillNotDraw(false) setOnApplyWindowInsetsListener(this) { _, insets -> @@ -162,6 +164,9 @@ class TableScreen( tableView.updatePadding( bottom = insets.navigationBars.bottom ) + searchResultView.updatePadding( + bottom = insets.navigationBars.bottom + ) topInset = insets.statusBars.top insets } @@ -169,7 +174,7 @@ class TableScreen( addView(searchBar, frameParams(matchParent, 50.dp(context), marginTop = 42.dp(context))) addView(divider, frameParams(matchParent, 1, marginTop = 92.dp(context))) addView(tableView, frameParams(matchParent, matchParent, marginTop = 92.dp(context))) - addView(searchResultView, frameParams(matchParent, matchParent, marginTop = 92.dp(context))) + addView(searchResultView, frameParams(matchParent, matchParent, marginTop = 92.dp(context), marginBottom = (-42).dp(context))) launch { tableHelper.loadChars(fast = true) tableAdapter.notifyDataSetChanged() From 2916b64f47324f121d4f1542a7198585bc76e564 Mon Sep 17 00:00:00 2001 From: vadiole Date: Sat, 15 Jul 2023 11:09:35 +0200 Subject: [PATCH 23/26] fix deeplinks --- app/src/main/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e541043..0baf699 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -27,7 +27,7 @@ - + From 25bb72c7f98747212bc711d18d5e20b46ef404cc Mon Sep 17 00:00:00 2001 From: vadiole Date: Sat, 15 Jul 2023 22:15:50 +0200 Subject: [PATCH 24/26] fix NPE --- app/src/main/kotlin/vadiole/unicode/ui/table/CharRow.kt | 2 +- app/src/main/kotlin/vadiole/unicode/ui/table/TableView.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/vadiole/unicode/ui/table/CharRow.kt b/app/src/main/kotlin/vadiole/unicode/ui/table/CharRow.kt index 2942b02..bf6fc74 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/table/CharRow.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/table/CharRow.kt @@ -69,7 +69,7 @@ class CharRow( clearHighlight() charRipples[index] = true highlightIndex = index - handler.postDelayed(clearHighlightRunnable, 1000) + handler.postDelayed(clearHighlightRunnable, 2000) } private fun clearHighlight() { diff --git a/app/src/main/kotlin/vadiole/unicode/ui/table/TableView.kt b/app/src/main/kotlin/vadiole/unicode/ui/table/TableView.kt index 350255b..9f2d862 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/table/TableView.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/table/TableView.kt @@ -48,7 +48,7 @@ class TableView( val offset = measuredHeight / 2 tableLayoutManager.scrollToPositionWithOffset(row, offset) doOnNextLayout { - val cell = tableLayoutManager.findViewByPosition(row) as CharRow + val cell = tableLayoutManager.findViewByPosition(row) as? CharRow ?: return@doOnNextLayout cell.highlightChar(indexInRow) } } From 8affc9870bc09cd7251a47335a57be94a76d0f2a Mon Sep 17 00:00:00 2001 From: vadiole Date: Sat, 15 Jul 2023 22:16:45 +0200 Subject: [PATCH 25/26] bump version --- app/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c7830af..349b0dd 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -10,8 +10,8 @@ android { applicationId = "vadiole.unicode" minSdk = 26 targetSdk = 33 - versionCode = 132 - versionName = "1.3.2" + versionCode = 140 + versionName = "1.4.0" resourceConfigurations.addAll(listOf("en")) setProperty("archivesBaseName", "unicode-v$versionName") } From 805edb128f525a8b0f35d38c57775197dc32d672 Mon Sep 17 00:00:00 2001 From: vadiole Date: Sat, 15 Jul 2023 22:25:26 +0200 Subject: [PATCH 26/26] fix overscroll --- .../kotlin/vadiole/unicode/ui/common/CollectionView.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/vadiole/unicode/ui/common/CollectionView.kt b/app/src/main/kotlin/vadiole/unicode/ui/common/CollectionView.kt index 0d6cee6..1b8f1dd 100644 --- a/app/src/main/kotlin/vadiole/unicode/ui/common/CollectionView.kt +++ b/app/src/main/kotlin/vadiole/unicode/ui/common/CollectionView.kt @@ -1,6 +1,7 @@ package vadiole.unicode.ui.common import android.content.Context +import android.os.Build import android.view.View import androidx.recyclerview.widget.RecyclerView @@ -9,7 +10,11 @@ open class CollectionView(context: Context) : RecyclerView(context) { var isScrollEnabled = true init { - overScrollMode = View.OVER_SCROLL_ALWAYS + overScrollMode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + OVER_SCROLL_ALWAYS + } else { + OVER_SCROLL_NEVER + } clipToPadding = false clipChildren = false itemAnimator = null