diff --git a/CHANGELOG.md b/CHANGELOG.md
index aa46659..bb47c64 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
# Changelog
+## 1.16.5-chip-scroll — 2026-08-05
+
+- Action chips: icon + label; horizontal scroll actually works (child was MATCH_PARENT and clipped)
+- Peek nudge + visible scrollbar so overflow is obvious
+- Slightly taller result sheet so chips are not clipped
+
## 1.16.4-light-scrim — 2026-08-05
- Draw mode scrim covers the full screen (was inset under status/nav)
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 9f6b6d6..06bd2e9 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -11,8 +11,8 @@ android {
applicationId = "com.example.crkl"
minSdk = 27
targetSdk = 34
- versionCode = 21
- versionName = "1.16.4-light-scrim"
+ versionCode = 22
+ versionName = "1.16.5-chip-scroll"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
diff --git a/app/src/main/kotlin/com/example/crkl/accessibility/CrklAccessibilityService.kt b/app/src/main/kotlin/com/example/crkl/accessibility/CrklAccessibilityService.kt
index a6d16d2..63933da 100644
--- a/app/src/main/kotlin/com/example/crkl/accessibility/CrklAccessibilityService.kt
+++ b/app/src/main/kotlin/com/example/crkl/accessibility/CrklAccessibilityService.kt
@@ -184,7 +184,7 @@ class CrklAccessibilityService : AccessibilityService() {
private fun resultPanelParams(): WindowManager.LayoutParams {
val metrics = resources.displayMetrics
val width = (metrics.widthPixels * 0.94f).toInt()
- val height = (metrics.heightPixels * 0.48f).toInt()
+ val height = (metrics.heightPixels * 0.54f).toInt()
val nav = navigationBarHeight()
return WindowManager.LayoutParams(
width,
diff --git a/app/src/main/kotlin/com/example/crkl/ui/ResultPanelView.kt b/app/src/main/kotlin/com/example/crkl/ui/ResultPanelView.kt
index 97a069f..baa36e6 100644
--- a/app/src/main/kotlin/com/example/crkl/ui/ResultPanelView.kt
+++ b/app/src/main/kotlin/com/example/crkl/ui/ResultPanelView.kt
@@ -13,6 +13,7 @@ import android.widget.HorizontalScrollView
import android.widget.LinearLayout
import android.widget.ScrollView
import android.widget.TextView
+import com.example.crkl.R
/**
* Bottom-sheet style result card: title, text, five VIP chips, quiet status.
@@ -129,24 +130,37 @@ class ResultPanelView(
val actionsRow = LinearLayout(context).apply {
orientation = HORIZONTAL
gravity = Gravity.CENTER_VERTICAL
+ setPadding(0, 0, dp(28), 0) // peek next chip past the fade
}
- fun solidChip(label: String, bg: Int, onClick: () -> Unit): TextView =
- TextView(context).apply {
+ fun actionChip(
+ label: String,
+ iconRes: Int,
+ bg: Int,
+ onClick: () -> Unit
+ ): TextView {
+ val icon = context.getDrawable(iconRes)?.mutate()?.apply {
+ setBounds(0, 0, dp(16), dp(16))
+ }
+ return TextView(context).apply {
text = label
setTextColor(CrklUi.ChipFg)
- setTextSize(TypedValue.COMPLEX_UNIT_SP, 13f)
+ setTextSize(TypedValue.COMPLEX_UNIT_SP, 12.5f)
typeface = Typeface.DEFAULT_BOLD
- setPadding(dp(14), dp(11), dp(14), dp(11))
+ setCompoundDrawables(icon, null, null, null)
+ compoundDrawablePadding = dp(6)
+ gravity = Gravity.CENTER_VERTICAL
+ setPadding(dp(10), dp(9), dp(12), dp(9))
background = GradientDrawable().apply {
setColor(bg)
- cornerRadius = dp(20).toFloat()
+ cornerRadius = dp(18).toFloat()
}
val lp = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
- lp.marginEnd = dp(8)
+ lp.marginEnd = dp(6)
layoutParams = lp
isClickable = true
isFocusable = true
+ contentDescription = label
setOnClickListener {
statusView.visibility = VISIBLE
statusView.setTextColor(CrklUi.Mist)
@@ -154,29 +168,64 @@ class ResultPanelView(
onClick()
}
}
+ }
// VIP only — Email / Calendar stay on voice
if (onTranslate != null) {
- actionsRow.addView(solidChip("Translate", CrklUi.TealDeep, onTranslate))
+ actionsRow.addView(
+ actionChip("Translate", R.drawable.ic_chip_translate, CrklUi.TealDeep, onTranslate)
+ )
}
if (onCopy != null) {
- actionsRow.addView(solidChip("Copy", CrklUi.InkSoft, onCopy))
+ actionsRow.addView(
+ actionChip("Copy", R.drawable.ic_chip_copy, CrklUi.InkSoft, onCopy)
+ )
}
if (onExplain != null) {
- actionsRow.addView(solidChip("Explain", CrklUi.Ink, onExplain))
+ actionsRow.addView(
+ actionChip("Explain", R.drawable.ic_chip_explain, CrklUi.Ink, onExplain)
+ )
}
if (onShareList != null) {
- actionsRow.addView(solidChip("Share", CrklUi.Mist, onShareList))
+ actionsRow.addView(
+ actionChip("Share", R.drawable.ic_chip_share, CrklUi.Mist, onShareList)
+ )
}
if (onAddTodo != null) {
- actionsRow.addView(solidChip("Vikunja", CrklUi.Ok, onAddTodo))
+ actionsRow.addView(
+ actionChip("Vikunja", R.drawable.ic_chip_todo, CrklUi.Ok, onAddTodo)
+ )
}
- val actionsScroll = HorizontalScrollView(context).apply {
- isHorizontalScrollBarEnabled = false
- overScrollMode = OVER_SCROLL_NEVER
- setPadding(0, dp(10), 0, dp(2))
- addView(actionsRow)
+ val actionsScroll = object : HorizontalScrollView(context) {
+ override fun onInterceptTouchEvent(ev: android.view.MotionEvent): Boolean {
+ when (ev.actionMasked) {
+ android.view.MotionEvent.ACTION_DOWN,
+ android.view.MotionEvent.ACTION_MOVE ->
+ parent?.requestDisallowInterceptTouchEvent(true)
+ }
+ return super.onInterceptTouchEvent(ev)
+ }
+ }.apply {
+ isHorizontalScrollBarEnabled = true
+ isScrollbarFadingEnabled = false
+ scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
+ overScrollMode = OVER_SCROLL_IF_CONTENT_SCROLLS
+ isFillViewport = false
+ clipToPadding = false
+ setPadding(0, dp(8), dp(4), dp(6))
+ // WRAP_CONTENT is required — MATCH_PARENT (default) clips chips instead of scrolling.
+ addView(
+ actionsRow,
+ LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
+ )
+ // Nudge once so the first overflow chip peeks (affordance).
+ post {
+ if (actionsRow.measuredWidth > width && scrollX == 0) {
+ smoothScrollBy(dp(28), 0)
+ postDelayed({ smoothScrollTo(0, 0) }, 450)
+ }
+ }
}
listenView = TextView(context).apply {
diff --git a/app/src/main/res/drawable/ic_chip_copy.xml b/app/src/main/res/drawable/ic_chip_copy.xml
new file mode 100644
index 0000000..9d72852
--- /dev/null
+++ b/app/src/main/res/drawable/ic_chip_copy.xml
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_chip_explain.xml b/app/src/main/res/drawable/ic_chip_explain.xml
new file mode 100644
index 0000000..3d6b044
--- /dev/null
+++ b/app/src/main/res/drawable/ic_chip_explain.xml
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_chip_share.xml b/app/src/main/res/drawable/ic_chip_share.xml
new file mode 100644
index 0000000..6a49a02
--- /dev/null
+++ b/app/src/main/res/drawable/ic_chip_share.xml
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_chip_todo.xml b/app/src/main/res/drawable/ic_chip_todo.xml
new file mode 100644
index 0000000..a129268
--- /dev/null
+++ b/app/src/main/res/drawable/ic_chip_todo.xml
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_chip_translate.xml b/app/src/main/res/drawable/ic_chip_translate.xml
new file mode 100644
index 0000000..ad7686c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_chip_translate.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/scripts/smoke-circle.sh b/scripts/smoke-circle.sh
index d30a95d..77f4aa9 100755
--- a/scripts/smoke-circle.sh
+++ b/scripts/smoke-circle.sh
@@ -147,9 +147,31 @@ echo "$logs" | grep -qE 'extract:.*chars=[1-9]' || die "case2: no text extracted
pass "email region extract"
"$ADB" logcat -c
-"$ADB" shell input tap 320 2185
-sleep 1
-"$ADB" logcat -d | grep -q 'action kind=COPY' || die "Copy chip not triggered"
+# Chip row ~y=2100 on 1080x2400 with 54% sheet; Copy sits between Translate and Explain.
+copied=0
+for y in 2090 2100 2110; do
+ for x in 340 360 320 300 380; do
+ "$ADB" shell input tap "$x" "$y"
+ sleep 0.35
+ if "$ADB" logcat -d | grep -q 'action kind=COPY'; then
+ copied=1
+ break 2
+ fi
+ done
+done
+if [[ "$copied" != 1 ]]; then
+ "$ADB" shell input swipe 200 2100 700 2100 200
+ sleep 0.3
+ for x in 340 360 320; do
+ "$ADB" shell input tap "$x" 2100
+ sleep 0.35
+ if "$ADB" logcat -d | grep -q 'action kind=COPY'; then
+ copied=1
+ break
+ fi
+ done
+fi
+[[ "$copied" = 1 ]] || die "Copy chip not triggered"
"$ADB" logcat -d | grep -q 'action result ok=true' || die "Copy failed"
pass "Copy chip"