fix(calls): bound + unclip the answer drag — disc rides a clamped track (trackMax/lateralMax), clipChildren=false up the chain, halo follows; no clip-square, no full-screen runaway

This commit is contained in:
heaven 2026-06-24 02:54:58 +03:00
parent fe32bed081
commit 628618aafe
2 changed files with 35 additions and 10 deletions

View file

@ -250,6 +250,13 @@ public class IncomingCallActivity extends Activity {
// made "slide to answer" indistinguishable from a twitch. dp(88) a disc
// diameter of climb. needsDevice: tune one-handed reach on a tall phone.
final float commitDistance = Math.max(touchSlop * 4f, dp(88f));
// The disc rides a BOUNDED track. trackMax caps how far up it can follow the
// finger (progress p already saturates at the commit point, so chasing the
// finger further just slides the disc off into empty screen); lateralMax
// keeps the slide on its vertical rail. Without these the disc trails the
// finger 1:1 across the whole screen.
final float trackMax = commitDistance;
final float lateralMax = dp(24f);
return new View.OnTouchListener() {
private float downX;
private float downY;
@ -279,10 +286,11 @@ public class IncomingCallActivity extends Activity {
if (dragging) {
float up = -dy; // upward = positive progress
float p = (float) Math.min(1.0, Math.max(0.0, up / commitDistance));
// Track the finger UP its rail: a little lateral follow,
// but the disc never travels downward (down = no commit).
disc.setTranslationX(0.15f * dx);
disc.setTranslationY(Math.min(0f, dy));
// Follow the finger UP its rail, clamped to the track: the
// disc never travels downward and never past trackMax, with
// only a small clamped lateral give.
disc.setTranslationX(Math.max(-lateralMax, Math.min(lateralMax, 0.15f * dx)));
disc.setTranslationY(-Math.min(Math.max(up, 0f), trackMax));
applyProgressVisuals(disc, glow, decline, p);
hapticLadder(disc, p);
}
@ -327,6 +335,10 @@ public class IncomingCallActivity extends Activity {
float gs = 0.58f + 0.42f * p; // peak 1.0 (the 124dp glow already covers the disc)
glow.setScaleX(gs);
glow.setScaleY(gs);
// Keep the halo glued behind the moving disc otherwise it blooms at the
// rest position while the disc slides up and away, decoupling the two.
glow.setTranslationX(disc.getTranslationX());
glow.setTranslationY(disc.getTranslationY());
}
if (decline != null) {
// The screen "takes a side": the decline disc recedes as answer climbs.
@ -381,6 +393,7 @@ public class IncomingCallActivity extends Activity {
}
if (glow != null) {
glow.animate().alpha(0f).scaleX(0.58f).scaleY(0.58f)
.translationX(0f).translationY(0f)
.setInterpolator(new DecelerateInterpolator())
.setDuration(dur + 60)
.start();

View file

@ -11,7 +11,9 @@
android:gravity="center_horizontal"
android:fitsSystemWindows="true"
android:paddingStart="24dp"
android:paddingEnd="24dp">
android:paddingEnd="24dp"
android:clipChildren="false"
android:clipToPadding="false">
<Space
android:layout_width="0dp"
@ -19,10 +21,12 @@
android:layout_weight="1.1" />
<!-- Avatar disc (caller initial, set in code) behind a breathing halo. Sized
124dp to match the over-lock hero avatar so answer-from-lock doesn't pop. -->
124dp to match the over-lock hero avatar so answer-from-lock doesn't pop.
clipChildren=false so the pulsing halo can bloom past the 160dp box. -->
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:clipChildren="false">
<View
android:id="@+id/call_avatar_halo"
@ -72,12 +76,18 @@
android:layout_height="0dp"
android:layout_weight="2" />
<!-- clipChildren/clipToPadding=false so the answer disc can slide UP out of the
action row (and the column + FrameLayout below) without being clipped at a
container edge — otherwise the dragged disc vanishes behind the 124dp glow
box. The whole ancestor chain to the root must opt out. -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingBottom="56dp"
android:weightSum="2">
android:weightSum="2"
android:clipChildren="false"
android:clipToPadding="false">
<!-- Decline -->
<LinearLayout
@ -127,7 +137,8 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:gravity="center">
android:gravity="center"
android:clipChildren="false">
<ImageView
android:id="@+id/answer_hint"
@ -140,7 +151,8 @@
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:clipChildren="false">
<View
android:id="@+id/answer_glow"