Make VIP chips scrollable with icons
Chip row was MATCH_PARENT so Share/Vikunja clipped with no scroll. Add icon+label chips, real horizontal scroll, peek nudge, taller sheet.
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M7,4h10c1.1,0 2,0.9 2,2v14l-3,-2 -3,2 -3,-2 -3,2V6c0,-1.1 0.9,-2 2,-2zM9,8h6v2H9V8zM9,12h6v2H9v-2z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M12,3a7,7 0 0 1 7,7c0,2.6 -1.4,4.4 -3.2,6.1 -0.7,0.7 -1.4,1.3 -1.9,2.1h-3.8c-0.5,-0.8 -1.2,-1.4 -1.9,-2.1C6.4,14.4 5,12.6 5,10a7,7 0 0 1 7,-7zM10,20h4v2h-4v-2z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M14,4v2h3.6L12,11.6 6.4,6H10V4H4v6h2V7.4L12,13.4 18,7.4V10h2V4h-6zM5,16h14v2H5v-2zM5,20h10v2H5v-2z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M6,5h12v2H6V5zM6,11h12v2H6v-2zM6,17h8v2H6v-2zM18.5,16.2l1.4,1.4 -3.2,3.2 -2,-2 1.4,-1.4 0.6,0.6 1.8,-1.8z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<!-- Simple A⇄文 mark -->
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M3,6h6v2H5.5l2.2,6h2.1L12,8h2l-3.2,9H8.6L6.5,11.2 4.4,17H2.2L5.4,8H3V6z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M14,7h7v2h-2.1l-0.9,2.4h2.2v2h-2.9L16,17h-2.2l1.5-3.6H13V11h1.9L16,7z" />
|
||||
</vector>
|
||||
+25
-3
@@ -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"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user