diff --git a/android/app/src/main/java/chat/vojo/app/IncomingCallActivity.java b/android/app/src/main/java/chat/vojo/app/IncomingCallActivity.java index 137f2d24..69b87961 100644 --- a/android/app/src/main/java/chat/vojo/app/IncomingCallActivity.java +++ b/android/app/src/main/java/chat/vojo/app/IncomingCallActivity.java @@ -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(); diff --git a/android/app/src/main/res/layout/activity_incoming_call.xml b/android/app/src/main/res/layout/activity_incoming_call.xml index ac4083fe..1285198c 100644 --- a/android/app/src/main/res/layout/activity_incoming_call.xml +++ b/android/app/src/main/res/layout/activity_incoming_call.xml @@ -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"> + 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. --> + android:layout_height="wrap_content" + android:clipChildren="false"> + + android:weightSum="2" + android:clipChildren="false" + android:clipToPadding="false"> + android:gravity="center" + android:clipChildren="false"> + android:layout_height="wrap_content" + android:clipChildren="false">