fix: prevent overlay pill bar from trembling by using fixed-height window
The WRAP_CONTENT window height caused WindowManager to relayout every frame during animateContentSize() animations. Use a fixed 350dp window with a touch-passthrough FrameLayout wrapper that forwards only touches within the actual pill bar bounds. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4282ac4cff
commit
18309c3f9d
1 changed files with 54 additions and 18 deletions
|
|
@ -5,9 +5,18 @@ import android.content.Intent
|
||||||
import android.graphics.PixelFormat
|
import android.graphics.PixelFormat
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
|
import androidx.compose.ui.layout.positionInWindow
|
||||||
import androidx.compose.ui.platform.ComposeView
|
import androidx.compose.ui.platform.ComposeView
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.lifecycle.LifecycleService
|
import androidx.lifecycle.LifecycleService
|
||||||
|
|
@ -46,7 +55,7 @@ class AssistantService : LifecycleService(), SavedStateRegistryOwner {
|
||||||
private lateinit var sttWrapper: SpeechRecognizerWrapper
|
private lateinit var sttWrapper: SpeechRecognizerWrapper
|
||||||
private lateinit var cameraCaptureManager: CameraCaptureManager
|
private lateinit var cameraCaptureManager: CameraCaptureManager
|
||||||
|
|
||||||
private var overlayView: ComposeView? = null
|
private var overlayView: View? = null
|
||||||
private val windowManager by lazy { getSystemService(WINDOW_SERVICE) as WindowManager }
|
private val windowManager by lazy { getSystemService(WINDOW_SERVICE) as WindowManager }
|
||||||
|
|
||||||
private val savedStateRegistryController = SavedStateRegistryController.create(this)
|
private val savedStateRegistryController = SavedStateRegistryController.create(this)
|
||||||
|
|
@ -112,9 +121,12 @@ class AssistantService : LifecycleService(), SavedStateRegistryOwner {
|
||||||
private fun addOverlay() {
|
private fun addOverlay() {
|
||||||
if (overlayView != null) return
|
if (overlayView != null) return
|
||||||
|
|
||||||
|
val density = resources.displayMetrics.density
|
||||||
|
val fixedHeightPx = (350 * density).toInt()
|
||||||
|
|
||||||
val params = WindowManager.LayoutParams(
|
val params = WindowManager.LayoutParams(
|
||||||
WindowManager.LayoutParams.MATCH_PARENT,
|
WindowManager.LayoutParams.MATCH_PARENT,
|
||||||
WindowManager.LayoutParams.WRAP_CONTENT,
|
fixedHeightPx,
|
||||||
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
|
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
|
||||||
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
|
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
|
||||||
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
|
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
|
||||||
|
|
@ -123,26 +135,50 @@ class AssistantService : LifecycleService(), SavedStateRegistryOwner {
|
||||||
gravity = Gravity.BOTTOM
|
gravity = Gravity.BOTTOM
|
||||||
}
|
}
|
||||||
|
|
||||||
val view = ComposeView(this).apply {
|
val wrapper = object : FrameLayout(this@AssistantService) {
|
||||||
setViewTreeLifecycleOwner(this@AssistantService)
|
@Volatile var contentTop = fixedHeightPx
|
||||||
setViewTreeSavedStateRegistryOwner(this@AssistantService)
|
private var gestureInContent = false
|
||||||
|
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
|
||||||
setContent {
|
if (ev.actionMasked == MotionEvent.ACTION_DOWN) {
|
||||||
PicoClawTheme {
|
gestureInContent = ev.y >= contentTop
|
||||||
val state by assistantManager.state.collectAsState()
|
|
||||||
AssistantPillBar(
|
|
||||||
state = state,
|
|
||||||
onClose = { shutdown() },
|
|
||||||
onInterrupt = { assistantManager.interrupt() },
|
|
||||||
onCameraToggle = { assistantManager.toggleCamera() },
|
|
||||||
cameraCaptureManager = cameraCaptureManager
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
if (!gestureInContent) return false
|
||||||
|
return super.dispatchTouchEvent(ev)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
windowManager.addView(view, params)
|
wrapper.setViewTreeLifecycleOwner(this)
|
||||||
overlayView = view
|
wrapper.setViewTreeSavedStateRegistryOwner(this)
|
||||||
|
|
||||||
|
ComposeView(this).apply {
|
||||||
|
setContent {
|
||||||
|
PicoClawTheme {
|
||||||
|
val state by assistantManager.state.collectAsState()
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentAlignment = Alignment.BottomCenter
|
||||||
|
) {
|
||||||
|
AssistantPillBar(
|
||||||
|
state = state,
|
||||||
|
onClose = { shutdown() },
|
||||||
|
onInterrupt = { assistantManager.interrupt() },
|
||||||
|
onCameraToggle = { assistantManager.toggleCamera() },
|
||||||
|
cameraCaptureManager = cameraCaptureManager,
|
||||||
|
modifier = Modifier.onGloballyPositioned { coordinates ->
|
||||||
|
wrapper.contentTop = coordinates.positionInWindow().y.toInt()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wrapper.addView(this, FrameLayout.LayoutParams(
|
||||||
|
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
FrameLayout.LayoutParams.MATCH_PARENT
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
windowManager.addView(wrapper, params)
|
||||||
|
overlayView = wrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeOverlay() {
|
private fun removeOverlay() {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue