Skip to content

Commit 292321d

Browse files
authored
Merge pull request #68 from jamakr4/20-refactor-window-resizing
feat: improve tile resize controls
2 parents f074e27 + 1d8b9f4 commit 292321d

9 files changed

Lines changed: 334 additions & 43 deletions

File tree

‎app/build.gradle‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ android {
1414
applicationId "com.drivehub.kamera"
1515
minSdk 28
1616
targetSdk 36
17-
versionCode 13
18-
versionName "0.8.6"
17+
versionCode 15
18+
versionName "0.8.7"
1919
buildConfigField "boolean", "IS_BETA", "true"
2020

2121
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.drivehub.kamera.camera;
2+
3+
final class OverlayResizeMath {
4+
5+
static final float SENSITIVITY = 1.5f;
6+
7+
private OverlayResizeMath() {
8+
}
9+
10+
static float updateTargetWidth(float currentWidth, float scaleFactor,
11+
float minWidth, float maxWidth) {
12+
if (!Float.isFinite(currentWidth)
13+
|| !Float.isFinite(scaleFactor)
14+
|| scaleFactor <= 0f
15+
|| !Float.isFinite(minWidth)
16+
|| !Float.isFinite(maxWidth)
17+
|| minWidth > maxWidth) {
18+
return currentWidth;
19+
}
20+
21+
double amplifiedFactor = Math.pow(scaleFactor, SENSITIVITY);
22+
double nextWidth = currentWidth * amplifiedFactor;
23+
if (!Double.isFinite(nextWidth)) {
24+
return currentWidth;
25+
}
26+
if (nextWidth <= minWidth) {
27+
return minWidth;
28+
}
29+
if (nextWidth >= maxWidth) {
30+
return maxWidth;
31+
}
32+
return (float) nextWidth;
33+
}
34+
}

‎app/src/main/java/com/drivehub/kamera/camera/OverlayService.java‎

Lines changed: 143 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import android.os.IBinder;
2222
import android.os.Looper;
2323
import android.util.Log;
24+
import android.view.Choreographer;
2425
import android.view.Gravity;
2526
import android.view.MotionEvent;
2627
import android.view.ScaleGestureDetector;
@@ -95,7 +96,19 @@ public class OverlayService extends Service implements TextureView.SurfaceTextur
9596
private int overlayMode = OVERLAY_MODE_SAIC;
9697

9798
private ScaleGestureDetector scaleGestureDetector;
99+
private float resizeTargetWidthPx = DEFAULT_OVERLAY_WIDTH_PX;
100+
private boolean resizePending;
101+
private boolean resizeFrameScheduled;
102+
private boolean saveLayoutAfterResizeFrame;
98103
private final Handler mainHandler = new Handler(Looper.getMainLooper());
104+
private final Choreographer.FrameCallback resizeFrameCallback = frameTimeNanos -> {
105+
resizeFrameScheduled = false;
106+
applyPendingResize();
107+
if (saveLayoutAfterResizeFrame) {
108+
saveLayoutAfterResizeFrame = false;
109+
saveOverlayLayoutPrefs();
110+
}
111+
};
99112
private final Runnable foregroundModePollRunnable = new Runnable() {
100113
@Override
101114
public void run() {
@@ -121,6 +134,9 @@ public void run() {
121134
updateOverlayPresentation(true);
122135
} else if (UiPrefs.KEY_DEV_FOREGROUND_MODE_POLL_MS.equals(key)) {
123136
restartForegroundModePolling();
137+
} else if (UiPrefs.KEY_OVERLAY_RESIZE_LOCKED.equals(key)
138+
&& UiPrefs.isOverlayResizeLocked(sharedPreferences)) {
139+
cancelPendingResize();
124140
} else if (UiPrefs.KEY_SMOOTH_ENTRY.equals(key)
125141
&& !UiPrefs.isSmoothEntryEnabled(sharedPreferences)
126142
&& overlayView != null) {
@@ -277,25 +293,43 @@ private void showFloatingWindow() {
277293

278294
scaleGestureDetector = new ScaleGestureDetector(this,
279295
new ScaleGestureDetector.SimpleOnScaleGestureListener() {
296+
@Override
297+
public boolean onScaleBegin(ScaleGestureDetector detector) {
298+
if (uiPrefs != null && UiPrefs.isOverlayResizeLocked(uiPrefs)) {
299+
return false;
300+
}
301+
if (!resizePending) {
302+
resizeTargetWidthPx = overlayWidthPx;
303+
}
304+
saveLayoutAfterResizeFrame = false;
305+
return true;
306+
}
307+
280308
@Override
281309
public boolean onScale(ScaleGestureDetector detector) {
310+
if (uiPrefs != null && UiPrefs.isOverlayResizeLocked(uiPrefs)) {
311+
return true;
312+
}
282313
float factor = detector.getScaleFactor();
283-
if (factor <= 0f || Float.isNaN(factor)) return true;
284-
int newW = Math.round(overlayWidthPx * factor);
285-
int[] wh = clampOverlaySize(newW);
286-
overlayWidthPx = wh[0];
287-
overlayHeightPx = wh[1];
288-
overlayParams.width = overlayWidthPx;
289-
overlayParams.height = overlayHeightPx;
290-
clampOverlayPositionToScreen();
291-
windowManager.updateViewLayout(overlayView, overlayParams);
292-
applyPreviewTransform();
314+
int minWidth = clampOverlaySize(OVERLAY_MIN_WIDTH_PX)[0];
315+
int maxWidth = clampOverlaySize(OVERLAY_MAX_WIDTH_PX)[0];
316+
float nextTargetWidth = OverlayResizeMath.updateTargetWidth(
317+
resizeTargetWidthPx,
318+
factor,
319+
minWidth,
320+
maxWidth
321+
);
322+
if (Float.compare(nextTargetWidth, resizeTargetWidthPx) != 0) {
323+
resizeTargetWidthPx = nextTargetWidth;
324+
resizePending = true;
325+
scheduleResizeFrame();
326+
}
293327
return true;
294328
}
295329

296330
@Override
297331
public void onScaleEnd(ScaleGestureDetector detector) {
298-
saveOverlayLayoutPrefs();
332+
finishResizeGesture();
299333
}
300334
});
301335

@@ -315,12 +349,12 @@ public void onScaleEnd(ScaleGestureDetector detector) {
315349
int action = event.getActionMasked();
316350
int pointerCount = event.getPointerCount();
317351

352+
if (action == MotionEvent.ACTION_CANCEL) {
353+
finishResizeGesture();
354+
return true;
355+
}
356+
318357
if (pointerCount >= 2) {
319-
if (action == MotionEvent.ACTION_POINTER_UP
320-
|| action == MotionEvent.ACTION_CANCEL
321-
|| action == MotionEvent.ACTION_UP) {
322-
saveOverlayLayoutPrefs();
323-
}
324358
return true;
325359
}
326360

@@ -335,6 +369,9 @@ public void onScaleEnd(ScaleGestureDetector detector) {
335369
if (scaleGestureDetector.isInProgress()) {
336370
return true;
337371
}
372+
if (uiPrefs != null && UiPrefs.isOverlayMoveLocked(uiPrefs)) {
373+
return true;
374+
}
338375
float dx = event.getRawX() - initialTouchX;
339376
float dy = event.getRawY() - initialTouchY;
340377
int newX = (int) (initialX + dx);
@@ -345,7 +382,7 @@ public void onScaleEnd(ScaleGestureDetector detector) {
345382
windowManager.updateViewLayout(overlayView, overlayParams);
346383
return true;
347384
case MotionEvent.ACTION_UP:
348-
saveOverlayLayoutPrefs();
385+
finishResizeGesture();
349386
float tapDx = event.getRawX() - initialTouchX;
350387
float tapDy = event.getRawY() - initialTouchY;
351388
boolean isTap = (tapDx * tapDx + tapDy * tapDy) < (float) (dpToPx(12) * dpToPx(12));
@@ -366,6 +403,62 @@ public void onScaleEnd(ScaleGestureDetector detector) {
366403
});
367404
}
368405

406+
private void scheduleResizeFrame() {
407+
if (resizeFrameScheduled) return;
408+
resizeFrameScheduled = true;
409+
Choreographer.getInstance().postFrameCallback(resizeFrameCallback);
410+
}
411+
412+
private void finishResizeGesture() {
413+
if (!resizePending) {
414+
if (!resizeFrameScheduled) {
415+
saveOverlayLayoutPrefs();
416+
} else {
417+
saveLayoutAfterResizeFrame = true;
418+
}
419+
return;
420+
}
421+
saveLayoutAfterResizeFrame = true;
422+
scheduleResizeFrame();
423+
}
424+
425+
private void applyPendingResize() {
426+
if (!resizePending || overlayParams == null || overlayView == null || windowManager == null) {
427+
return;
428+
}
429+
resizePending = false;
430+
431+
int requestedWidth = Math.round(resizeTargetWidthPx);
432+
int[] wh = clampOverlaySize(requestedWidth);
433+
if (wh[0] != requestedWidth) {
434+
resizeTargetWidthPx = wh[0];
435+
}
436+
if (overlayWidthPx == wh[0] && overlayHeightPx == wh[1]) {
437+
return;
438+
}
439+
440+
overlayWidthPx = wh[0];
441+
overlayHeightPx = wh[1];
442+
overlayParams.width = overlayWidthPx;
443+
overlayParams.height = overlayHeightPx;
444+
clampOverlayPositionToScreen();
445+
windowManager.updateViewLayout(overlayView, overlayParams);
446+
applyPreviewTransform();
447+
}
448+
449+
private void cancelResizeFrame() {
450+
if (!resizeFrameScheduled) return;
451+
Choreographer.getInstance().removeFrameCallback(resizeFrameCallback);
452+
resizeFrameScheduled = false;
453+
}
454+
455+
private void cancelPendingResize() {
456+
cancelResizeFrame();
457+
resizePending = false;
458+
saveLayoutAfterResizeFrame = false;
459+
resizeTargetWidthPx = overlayWidthPx;
460+
}
461+
369462
private View createOverlayCard() {
370463
FrameLayout card = new FrameLayout(this);
371464
card.setLayoutParams(new FrameLayout.LayoutParams(
@@ -560,26 +653,33 @@ private void updateOverlayPresentation(boolean persist) {
560653
private void applyPreviewTransform() {
561654
final TextureView tv = textureView;
562655
if (tv == null) return;
563-
tv.post(() -> {
564-
FrameLayout.LayoutParams params;
565-
if (shouldRotatePreviewToDrivingDirection()) {
566-
params = new FrameLayout.LayoutParams(
567-
overlayHeightPx,
568-
overlayWidthPx,
569-
Gravity.CENTER
570-
);
571-
} else {
572-
params = new FrameLayout.LayoutParams(
573-
ViewGroup.LayoutParams.MATCH_PARENT,
574-
ViewGroup.LayoutParams.MATCH_PARENT,
575-
Gravity.CENTER
576-
);
577-
}
578-
tv.setLayoutParams(params);
579-
tv.setScaleX(1f);
580-
tv.setScaleY(1f);
581-
tv.setRotation(getPreviewRotationDegrees());
582-
});
656+
if (Looper.myLooper() != Looper.getMainLooper()) {
657+
tv.post(() -> applyPreviewTransformTo(tv));
658+
return;
659+
}
660+
applyPreviewTransformTo(tv);
661+
}
662+
663+
private void applyPreviewTransformTo(TextureView tv) {
664+
boolean rotated = shouldRotatePreviewToDrivingDirection();
665+
int desiredWidth = rotated ? overlayHeightPx : ViewGroup.LayoutParams.MATCH_PARENT;
666+
int desiredHeight = rotated ? overlayWidthPx : ViewGroup.LayoutParams.MATCH_PARENT;
667+
float desiredRotation = getPreviewRotationDegrees();
668+
669+
ViewGroup.LayoutParams currentParams = tv.getLayoutParams();
670+
if (!(currentParams instanceof FrameLayout.LayoutParams)
671+
|| currentParams.width != desiredWidth
672+
|| currentParams.height != desiredHeight
673+
|| ((FrameLayout.LayoutParams) currentParams).gravity != Gravity.CENTER) {
674+
tv.setLayoutParams(new FrameLayout.LayoutParams(
675+
desiredWidth,
676+
desiredHeight,
677+
Gravity.CENTER
678+
));
679+
}
680+
if (tv.getScaleX() != 1f) tv.setScaleX(1f);
681+
if (tv.getScaleY() != 1f) tv.setScaleY(1f);
682+
if (tv.getRotation() != desiredRotation) tv.setRotation(desiredRotation);
583683
}
584684

585685
private void clampOverlayPositionToScreen() {
@@ -710,6 +810,12 @@ public void onDestroy() {
710810
uiPrefs.unregisterOnSharedPreferenceChangeListener(prefListener);
711811
}
712812
mainHandler.removeCallbacks(foregroundModePollRunnable);
813+
cancelResizeFrame();
814+
if (resizePending) {
815+
applyPendingResize();
816+
saveOverlayLayoutPrefs();
817+
}
818+
saveLayoutAfterResizeFrame = false;
713819
fadeOutPending = false;
714820
if (overlayView != null) {
715821
overlayView.animate().cancel();

‎app/src/main/java/com/drivehub/kamera/settings/SettingsDialogController.java‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ private Views bindViews(SharedPreferences prefs) {
178178
v.swDashcamEnabled = dialog.findViewById(R.id.switchDashcamEnabled);
179179
v.swSafetyWarning = dialog.findViewById(R.id.switchSafetyWarning);
180180
v.swSmoothEntry = dialog.findViewById(R.id.switchSmoothEntry);
181+
v.swLockTileResizing = dialog.findViewById(R.id.switchLockTileResizing);
182+
v.swLockTileMoving = dialog.findViewById(R.id.switchLockTileMoving);
181183
v.swOemAvmCoexist = dialog.findViewById(R.id.switchOemAvmCoexist);
182184
v.swAllowBetaUpdates = dialog.findViewById(R.id.switchAllowBetaUpdates);
183185
v.seekOverlayHideDelay = dialog.findViewById(R.id.seekOverlayHideDelay);
@@ -301,6 +303,16 @@ private Views bindViews(SharedPreferences prefs) {
301303
v.swSmoothEntry.setOnCheckedChangeListener((btn, checked) ->
302304
UiPrefs.setSmoothEntryEnabled(prefs, checked));
303305
}
306+
if (v.swLockTileResizing != null) {
307+
v.swLockTileResizing.setChecked(UiPrefs.isOverlayResizeLocked(prefs));
308+
v.swLockTileResizing.setOnCheckedChangeListener((btn, checked) ->
309+
UiPrefs.setOverlayResizeLocked(prefs, checked));
310+
}
311+
if (v.swLockTileMoving != null) {
312+
v.swLockTileMoving.setChecked(UiPrefs.isOverlayMoveLocked(prefs));
313+
v.swLockTileMoving.setOnCheckedChangeListener((btn, checked) ->
314+
UiPrefs.setOverlayMoveLocked(prefs, checked));
315+
}
304316
if (v.swOemAvmCoexist != null) {
305317
v.swOemAvmCoexist.setChecked(UiPrefs.isOemAvmCoexistEnabled(prefs));
306318
v.swOemAvmCoexist.setOnCheckedChangeListener((btn, checked) ->
@@ -410,6 +422,8 @@ private void wireAppearance(SharedPreferences prefs) {
410422
views.swBannerErrorRecovered,
411423
views.swSafetyWarning,
412424
views.swSmoothEntry,
425+
views.swLockTileResizing,
426+
views.swLockTileMoving,
413427
views.swAllowBetaUpdates
414428
},
415429
views.dialogClose,
@@ -650,6 +664,8 @@ private static final class Views {
650664
Switch swDashcamEnabled;
651665
Switch swSafetyWarning;
652666
Switch swSmoothEntry;
667+
Switch swLockTileResizing;
668+
Switch swLockTileMoving;
653669
Switch swOemAvmCoexist;
654670
Switch swAllowBetaUpdates;
655671
SeekBar seekOverlayHideDelay;

‎app/src/main/java/com/drivehub/kamera/settings/UiPrefs.java‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ public final class UiPrefs {
1212
public static final String KEY_ACCENT_COLOR = "accentColor";
1313
public static final String KEY_ALLOW_BETA_UPDATES = "allowBetaUpdates";
1414
public static final String KEY_SMOOTH_ENTRY = "smoothEntry";
15+
public static final String KEY_OVERLAY_RESIZE_LOCKED = "overlayResizeLocked";
16+
public static final String KEY_OVERLAY_MOVE_LOCKED = "overlayMoveLocked";
1517
public static final String KEY_OVERLAY_ON_SIGNAL = "overlayOnSignal";
1618
public static final String KEY_LANE_CHANGES_ONLY = "laneChangesOnly";
1719
public static final String KEY_LANE_CHANGE_MIN_SPEED_KMH = "laneChangeMinSpeedKmh";
@@ -173,6 +175,22 @@ public static void setSmoothEntryEnabled(SharedPreferences prefs, boolean enable
173175
prefs.edit().putBoolean(KEY_SMOOTH_ENTRY, enabled).apply();
174176
}
175177

178+
public static boolean isOverlayResizeLocked(SharedPreferences prefs) {
179+
return prefs.getBoolean(KEY_OVERLAY_RESIZE_LOCKED, false);
180+
}
181+
182+
public static void setOverlayResizeLocked(SharedPreferences prefs, boolean locked) {
183+
prefs.edit().putBoolean(KEY_OVERLAY_RESIZE_LOCKED, locked).apply();
184+
}
185+
186+
public static boolean isOverlayMoveLocked(SharedPreferences prefs) {
187+
return prefs.getBoolean(KEY_OVERLAY_MOVE_LOCKED, false);
188+
}
189+
190+
public static void setOverlayMoveLocked(SharedPreferences prefs, boolean locked) {
191+
prefs.edit().putBoolean(KEY_OVERLAY_MOVE_LOCKED, locked).apply();
192+
}
193+
176194
public static boolean isOemAvmCoexistEnabled(SharedPreferences prefs) {
177195
return prefs.getBoolean(KEY_OEM_AVM_COEXIST, true);
178196
}

0 commit comments

Comments
 (0)